From f03dc57faf51f88b524738c28e749d2ce73238fd Mon Sep 17 00:00:00 2001 From: Mark Nauwelaerts Date: Thu, 18 Nov 2010 16:48:06 +0100 Subject: [PATCH] qtmux: add mfra to fragmented file MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Based on patch by Marc-André Lureau --- gst/qtmux/atoms.c | 226 +++++++++++++++++++++++++++++++++++++++++++++++++++ gst/qtmux/atoms.h | 37 +++++++++ gst/qtmux/gstqtmux.c | 61 ++++++++++++-- gst/qtmux/gstqtmux.h | 5 ++ 4 files changed, 320 insertions(+), 9 deletions(-) diff --git a/gst/qtmux/atoms.c b/gst/qtmux/atoms.c index 8ea269f..7556078 100644 --- a/gst/qtmux/atoms.c +++ b/gst/qtmux/atoms.c @@ -3643,12 +3643,238 @@ atom_traf_add_samples (AtomTRAF * traf, guint32 delta, guint32 size, pts_offset); } +guint32 +atom_traf_get_sample_num (AtomTRAF * traf) +{ + AtomTRUN *trun; + + if (G_UNLIKELY (!traf->truns)) + return 0; + + trun = traf->truns->data; + return atom_array_get_len (&trun->entries); +} + void atom_moof_add_traf (AtomMOOF * moof, AtomTRAF * traf) { moof->trafs = g_list_append (moof->trafs, traf); } +static void +atom_tfra_free (AtomTFRA * tfra) +{ + atom_full_clear (&tfra->header); + atom_array_clear (&tfra->entries); + g_free (tfra); +} + +AtomMFRA * +atom_mfra_new (AtomsContext * context) +{ + AtomMFRA *mfra = g_new0 (AtomMFRA, 1); + + atom_header_set (&mfra->header, FOURCC_mfra, 0, 0); + return mfra; +} + +void +atom_mfra_add_tfra (AtomMFRA * mfra, AtomTFRA * tfra) +{ + mfra->tfras = g_list_append (mfra->tfras, tfra); +} + +void +atom_mfra_free (AtomMFRA * mfra) +{ + GList *walker; + + walker = mfra->tfras; + while (walker) { + atom_tfra_free ((AtomTFRA *) walker->data); + walker = g_list_next (walker); + } + g_list_free (mfra->tfras); + mfra->tfras = NULL; + + atom_clear (&mfra->header); + g_free (mfra); +} + +static void +atom_tfra_init (AtomTFRA * tfra, guint32 track_ID) +{ + guint8 flags[3] = { 0, 0, 0 }; + + atom_full_init (&tfra->header, FOURCC_tfra, 0, 0, 0, flags); + tfra->track_ID = track_ID; + atom_array_init (&tfra->entries, 512); +} + +AtomTFRA * +atom_tfra_new (AtomsContext * context, guint32 track_ID) +{ + AtomTFRA *tfra = g_new0 (AtomTFRA, 1); + + atom_tfra_init (tfra, track_ID); + return tfra; + +} + +static inline gint +need_bytes (guint32 num) +{ + gint n = 0; + + while (num >>= 8) + n++; + + return n; +} + +void +atom_tfra_add_entry (AtomTFRA * tfra, guint64 dts, guint32 sample_num) +{ + TFRAEntry entry; + + entry.time = dts; + /* fill in later */ + entry.moof_offset = 0; + /* always write a single trun in a single traf */ + entry.traf_number = 1; + entry.trun_number = 1; + entry.sample_number = sample_num; + + /* auto-use 64 bits if needed */ + if (dts > G_MAXUINT32) + tfra->header.version = 1; + + /* 1 byte will always do for traf and trun number, + * check how much sample_num needs */ + tfra->lengths = (tfra->lengths & 0xfc) || + MAX (tfra->lengths, need_bytes (sample_num)); + + atom_array_append (&tfra->entries, entry, 256); +} + +void +atom_tfra_update_offset (AtomTFRA * tfra, guint64 offset) +{ + gint i; + + /* auto-use 64 bits if needed */ + if (offset > G_MAXUINT32) + tfra->header.version = 1; + + for (i = atom_array_get_len (&tfra->entries) - 1; i >= 0; i--) { + TFRAEntry *entry = &atom_array_index (&tfra->entries, i); + + if (entry->moof_offset) + break; + entry->moof_offset = offset; + } +} + +static guint64 +atom_tfra_copy_data (AtomTFRA * tfra, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + guint32 i; + TFRAEntry *entry; + guint32 data; + guint bytes; + guint version; + + if (!atom_full_copy_data (&tfra->header, buffer, size, offset)) { + return 0; + } + + prop_copy_uint32 (tfra->track_ID, buffer, size, offset); + prop_copy_uint32 (tfra->lengths, buffer, size, offset); + prop_copy_uint32 (atom_array_get_len (&tfra->entries), buffer, size, offset); + + version = tfra->header.version; + for (i = 0; i < atom_array_get_len (&tfra->entries); ++i) { + entry = &atom_array_index (&tfra->entries, i); + if (version) { + prop_copy_uint64 (entry->time, buffer, size, offset); + prop_copy_uint64 (entry->moof_offset, buffer, size, offset); + } else { + prop_copy_uint32 (entry->time, buffer, size, offset); + prop_copy_uint32 (entry->moof_offset, buffer, size, offset); + } + + bytes = (tfra->lengths & (0x3 << 4)) + 1; + data = GUINT32_TO_BE (entry->traf_number); + prop_copy_fixed_size_string (((guint8 *) & data) + 4 - bytes, bytes, + buffer, size, offset); + + bytes = (tfra->lengths & (0x3 << 2)) + 1; + data = GUINT32_TO_BE (entry->trun_number); + prop_copy_fixed_size_string (((guint8 *) & data) + 4 - bytes, bytes, + buffer, size, offset); + + bytes = (tfra->lengths & (0x3)) + 1; + data = GUINT32_TO_BE (entry->sample_number); + prop_copy_fixed_size_string (((guint8 *) & data) + 4 - bytes, bytes, + buffer, size, offset); + + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_mfro_copy_data (guint32 s, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + guint8 flags[3] = { 0, 0, 0 }; + AtomFull mfro; + + atom_full_init (&mfro, FOURCC_mfro, 0, 0, 0, flags); + + if (!atom_full_copy_data (&mfro, buffer, size, offset)) { + return 0; + } + + prop_copy_uint32 (s, buffer, size, offset); + + atom_write_size (buffer, size, offset, original_offset); + + return *offset - original_offset; +} + + +guint64 +atom_mfra_copy_data (AtomMFRA * mfra, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + GList *walker; + + if (!atom_copy_data (&mfra->header, buffer, size, offset)) + return 0; + + walker = g_list_first (mfra->tfras); + while (walker != NULL) { + if (!atom_tfra_copy_data ((AtomTFRA *) walker->data, buffer, size, offset)) { + return 0; + } + walker = g_list_next (walker); + } + + /* 16 is the size of the mfro atom */ + if (!atom_mfro_copy_data (*offset - original_offset + 16, buffer, + size, offset)) + return 0; + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + /* some sample description construction helpers */ AtomInfo * diff --git a/gst/qtmux/atoms.h b/gst/qtmux/atoms.h index 980c897..8f48303 100644 --- a/gst/qtmux/atoms.h +++ b/gst/qtmux/atoms.h @@ -729,6 +729,33 @@ typedef struct _AtomWAVE GList *extension_atoms; } AtomWAVE; +typedef struct _TFRAEntry +{ + guint64 time; + guint64 moof_offset; + guint32 traf_number; + guint32 trun_number; + guint32 sample_number; +} TFRAEntry; + +typedef struct _AtomTFRA +{ + AtomFull header; + + guint32 track_ID; + guint32 lengths; + /* array of entries */ + ATOM_ARRAY (TFRAEntry) entries; +} AtomTFRA; + +typedef struct _AtomMFRA +{ + Atom header; + + /* list of tfra */ + GList *tfras; +} AtomMFRA; + /* * Function to serialize an atom */ @@ -812,8 +839,18 @@ void atom_traf_free (AtomTRAF * traf); void atom_traf_add_samples (AtomTRAF * traf, guint32 delta, guint32 size, gboolean sync, gboolean do_pts, gint64 pts_offset); +guint32 atom_traf_get_sample_num (AtomTRAF * traf); void atom_moof_add_traf (AtomMOOF *moof, AtomTRAF *traf); +AtomMFRA* atom_mfra_new (AtomsContext *context); +void atom_mfra_free (AtomMFRA *mfra); +AtomTFRA* atom_tfra_new (AtomsContext *context, guint32 track_ID); +void atom_tfra_add_entry (AtomTFRA *tfra, guint64 dts, guint32 sample_num); +void atom_tfra_update_offset (AtomTFRA * tfra, guint64 offset); +void atom_mfra_add_tfra (AtomMFRA *mfra, AtomTFRA *tfra); +guint64 atom_mfra_copy_data (AtomMFRA *mfra, guint8 **buffer, guint64 *size, guint64* offset); + + /* media sample description related helpers */ typedef struct diff --git a/gst/qtmux/gstqtmux.c b/gst/qtmux/gstqtmux.c index e00d25b..39fce4e 100644 --- a/gst/qtmux/gstqtmux.c +++ b/gst/qtmux/gstqtmux.c @@ -283,6 +283,9 @@ gst_qt_mux_pad_reset (GstQTPad * qtpad) qtpad->traf = NULL; } atom_array_clear (&qtpad->fragment_buffers); + + /* reference owned elsewhere */ + qtpad->tfra = NULL; } /* @@ -310,6 +313,10 @@ gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc) atom_moov_free (qtmux->moov); qtmux->moov = NULL; } + if (qtmux->mfra) { + atom_mfra_free (qtmux->mfra); + qtmux->mfra = NULL; + } if (qtmux->fast_start_file) { fclose (qtmux->fast_start_file); g_remove (qtmux->fast_start_file_path); @@ -1532,13 +1539,16 @@ gst_qt_mux_start_file (GstQTMux * qtmux) /* prepare moov and/or tags */ gst_qt_mux_configure_moov (qtmux, NULL); gst_qt_mux_setup_metadata (qtmux); - ret = gst_qt_mux_send_moov (qtmux, NULL, FALSE); + ret = gst_qt_mux_send_moov (qtmux, &qtmux->header_size, FALSE); if (ret != GST_FLOW_OK) return ret; /* extra atoms */ - ret = gst_qt_mux_send_extra_atoms (qtmux, TRUE, NULL, FALSE); + ret = + gst_qt_mux_send_extra_atoms (qtmux, TRUE, &qtmux->header_size, FALSE); if (ret != GST_FLOW_OK) return ret; + /* prepare index */ + qtmux->mfra = atom_mfra_new (qtmux->context); } else { /* extended to ensure some spare space */ ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0, TRUE); @@ -1595,6 +1605,20 @@ gst_qt_mux_stop_file (GstQTMux * qtmux) if (qtmux->fragment_sequence) { GstEvent *event; + if (qtmux->mfra) { + guint8 *data = NULL; + GstBuffer *buf; + + size = offset = 0; + GST_DEBUG_OBJECT (qtmux, "adding mfra"); + if (!atom_mfra_copy_data (qtmux->mfra, &data, &size, &offset)) + goto serialize_error; + buf = _gst_buffer_new_take_data (data, offset); + ret = gst_qt_mux_send_buffer (qtmux, buf, NULL, FALSE); + if (ret != GST_FLOW_OK) + return ret; + } + timescale = qtmux->timescale; /* only mvex duration is updated, * mvhd should be consistent with empty moov @@ -1732,8 +1756,9 @@ ftyp_error: static GstFlowReturn gst_qt_mux_pad_fragment_add_buffer (GstQTMux * qtmux, GstQTPad * pad, - GstBuffer * buf, gboolean force, guint32 nsamples, guint32 delta, - guint32 size, gboolean sync, gboolean do_pts, gint64 pts_offset) + GstBuffer * buf, gboolean force, guint32 nsamples, gint64 dts, + guint32 delta, guint32 size, gboolean sync, gboolean do_pts, + gint64 pts_offset) { GstFlowReturn ret = GST_FLOW_OK; @@ -1752,6 +1777,10 @@ flush: GstBuffer *buffer; guint i, total_size; + /* now we know where moof ends up, update offset in tfra */ + if (pad->tfra) + atom_tfra_update_offset (pad->tfra, qtmux->header_size); + moof = atom_moof_new (qtmux->context, qtmux->fragment_sequence); /* takes ownership */ atom_moof_add_traf (moof, pad->traf); @@ -1759,7 +1788,7 @@ flush: atom_moof_copy_data (moof, &data, &size, &offset); buffer = _gst_buffer_new_take_data (data, offset); GST_LOG_OBJECT (qtmux, "writing moof size %d", GST_BUFFER_SIZE (buffer)); - ret = gst_qt_mux_send_buffer (qtmux, buffer, NULL, FALSE); + ret = gst_qt_mux_send_buffer (qtmux, buffer, &qtmux->header_size, FALSE); /* and actual data */ total_size = 0; @@ -1771,11 +1800,13 @@ flush: GST_LOG_OBJECT (qtmux, "writing %d buffers, total_size %d", atom_array_get_len (&pad->fragment_buffers), total_size); if (ret == GST_FLOW_OK) - ret = gst_qt_mux_send_mdat_header (qtmux, NULL, total_size, FALSE); + ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, total_size, + FALSE); for (i = 0; i < atom_array_get_len (&pad->fragment_buffers); i++) { if (G_LIKELY (ret == GST_FLOW_OK)) ret = gst_qt_mux_send_buffer (qtmux, - atom_array_index (&pad->fragment_buffers, i), NULL, FALSE); + atom_array_index (&pad->fragment_buffers, i), &qtmux->header_size, + FALSE); else gst_buffer_unref (atom_array_index (&pad->fragment_buffers, i)); } @@ -1793,6 +1824,11 @@ init: atom_array_init (&pad->fragment_buffers, 512); pad->fragment_duration = gst_util_uint64_scale (qtmux->fragment_duration, atom_trak_get_timescale (pad->trak), 1000); + + if (G_UNLIKELY (qtmux->mfra && !pad->tfra)) { + pad->tfra = atom_tfra_new (qtmux->context, atom_trak_get_id (pad->trak)); + atom_mfra_add_tfra (qtmux->mfra, pad->tfra); + } } /* add buffer and metadata */ @@ -1800,6 +1836,13 @@ init: atom_array_append (&pad->fragment_buffers, buf, 256); pad->fragment_duration -= delta; + if (pad->tfra) { + guint32 sn = atom_traf_get_sample_num (pad->traf); + + if ((sync && pad->sync) || (sn == 1 && !pad->sync)) + atom_tfra_add_entry (pad->tfra, dts, sn); + } + if (G_UNLIKELY (force)) goto flush; @@ -2049,8 +2092,8 @@ gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf) if (qtmux->fragment_sequence) { /* ensure that always sync samples are marked as such */ return gst_qt_mux_pad_fragment_add_buffer (qtmux, pad, last_buf, - buf == NULL, nsamples, scaled_duration, sample_size, !pad->sync || sync, - do_pts, pts_offset); + buf == NULL, nsamples, last_dts, scaled_duration, sample_size, + !pad->sync || sync, do_pts, pts_offset); } else { atom_trak_add_samples (pad->trak, nsamples, scaled_duration, sample_size, chunk_offset, sync, do_pts, pts_offset); diff --git a/gst/qtmux/gstqtmux.h b/gst/qtmux/gstqtmux.h index 9856d77..76f9d23 100644 --- a/gst/qtmux/gstqtmux.h +++ b/gst/qtmux/gstqtmux.h @@ -113,6 +113,8 @@ struct _GstQTPad ATOM_ARRAY (GstBuffer *) fragment_buffers; /* running fragment duration */ gint64 fragment_duration; + /* optional fragment index book-keeping */ + AtomTFRA *tfra; /* if nothing is set, it won't be called */ GstQTPadPrepareBufferFunc prepare_buf_func; @@ -154,6 +156,9 @@ struct _GstQTMux GSList *extra_atoms; /* list of extra top-level atoms (e.g. UUID for xmp) * Stored as AtomInfo structs */ + /* fragmented file index */ + AtomMFRA *mfra; + /* fast start */ FILE *fast_start_file; -- 2.7.4