diff --git a/gst/quicktime/atoms.c b/gst/quicktime/atoms.c index b7b4a8ccc5..8ea269f57f 100644 --- a/gst/quicktime/atoms.c +++ b/gst/quicktime/atoms.c @@ -130,6 +130,12 @@ atom_full_free (AtomFull * full) g_free (full); } +static guint32 +atom_full_get_flags_as_uint (AtomFull * full) +{ + return full->flags[0] << 16 | full->flags[1] << 8 | full->flags[2]; +} + static void atom_full_set_flags_as_uint (AtomFull * full, guint32 flags_as_uint) { @@ -2593,6 +2599,12 @@ atom_trak_get_timescale (AtomTRAK * trak) return trak->mdia.mdhd.time_info.timescale; } +guint32 +atom_trak_get_id (AtomTRAK * trak) +{ + return trak->tkhd.track_ID; +} + static void atom_trak_set_id (AtomTRAK * trak, guint32 id) { @@ -3277,6 +3289,366 @@ atom_trak_set_video_type (AtomTRAK * trak, AtomsContext * context, } } +static void +atom_mfhd_init (AtomMFHD * mfhd, guint32 sequence_number) +{ + guint8 flags[3] = { 0, 0, 0 }; + + atom_full_init (&(mfhd->header), FOURCC_mfhd, 0, 0, 0, flags); + mfhd->sequence_number = sequence_number; +} + +static void +atom_moof_init (AtomMOOF * moof, AtomsContext * context, + guint32 sequence_number) +{ + atom_header_set (&moof->header, FOURCC_moof, 0, 0); + atom_mfhd_init (&moof->mfhd, sequence_number); + moof->trafs = NULL; +} + +AtomMOOF * +atom_moof_new (AtomsContext * context, guint32 sequence_number) +{ + AtomMOOF *moof = g_new0 (AtomMOOF, 1); + + atom_moof_init (moof, context, sequence_number); + return moof; +} + +static void +atom_trun_free (AtomTRUN * trun) +{ + atom_full_clear (&trun->header); + atom_array_clear (&trun->entries); + g_free (trun); +} + +void +atom_traf_free (AtomTRAF * traf) +{ + GList *walker; + + walker = traf->truns; + while (walker) { + atom_trun_free ((AtomTRUN *) walker->data); + walker = g_list_next (walker); + } + g_list_free (traf->truns); + traf->truns = NULL; + + g_free (traf); +} + +void +atom_moof_free (AtomMOOF * moof) +{ + GList *walker; + + walker = moof->trafs; + while (walker) { + atom_traf_free ((AtomTRAF *) walker->data); + walker = g_list_next (walker); + } + g_list_free (moof->trafs); + moof->trafs = NULL; + + g_free (moof); +} + +static guint64 +atom_mfhd_copy_data (AtomMFHD * mfhd, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_full_copy_data (&mfhd->header, buffer, size, offset)) { + return 0; + } + + prop_copy_uint32 (mfhd->sequence_number, buffer, size, offset); + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_tfhd_copy_data (AtomTFHD * tfhd, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + guint32 flags; + + if (!atom_full_copy_data (&tfhd->header, buffer, size, offset)) { + return 0; + } + + prop_copy_uint32 (tfhd->track_ID, buffer, size, offset); + + flags = atom_full_get_flags_as_uint (&tfhd->header); + + if (flags & TF_BASE_DATA_OFFSET) + prop_copy_uint64 (tfhd->base_data_offset, buffer, size, offset); + if (flags & TF_SAMPLE_DESCRIPTION_INDEX) + prop_copy_uint32 (tfhd->sample_description_index, buffer, size, offset); + if (flags & TF_DEFAULT_SAMPLE_DURATION) + prop_copy_uint32 (tfhd->default_sample_duration, buffer, size, offset); + if (flags & TF_DEFAULT_SAMPLE_SIZE) + prop_copy_uint32 (tfhd->default_sample_size, buffer, size, offset); + if (flags & TF_DEFAULT_SAMPLE_FLAGS) + prop_copy_uint32 (tfhd->default_sample_flags, buffer, size, offset); + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_trun_copy_data (AtomTRUN * trun, guint8 ** buffer, guint64 * size, + guint64 * offset, guint32 * data_offset) +{ + guint64 original_offset = *offset; + guint32 flags, i; + + flags = atom_full_get_flags_as_uint (&trun->header); + + /* if first trun in moof, forcibly add data_offset and record + * where it must be written later on */ + if (data_offset && !*data_offset) { + flags |= TR_DATA_OFFSET; + } else { + flags &= ~TR_DATA_OFFSET; + } + + atom_full_set_flags_as_uint (&trun->header, flags); + + if (!atom_full_copy_data (&trun->header, buffer, size, offset)) { + return 0; + } + + prop_copy_uint32 (trun->sample_count, buffer, size, offset); + + if (flags & TR_DATA_OFFSET) { + *data_offset = *offset; + prop_copy_int32 (trun->data_offset, buffer, size, offset); + } + if (flags & TR_FIRST_SAMPLE_FLAGS) + prop_copy_uint32 (trun->first_sample_flags, buffer, size, offset); + + for (i = 0; i < atom_array_get_len (&trun->entries); i++) { + TRUNSampleEntry *entry = &atom_array_index (&trun->entries, i); + + if (flags & TR_SAMPLE_DURATION) + prop_copy_uint32 (entry->sample_duration, buffer, size, offset); + if (flags & TR_SAMPLE_SIZE) + prop_copy_uint32 (entry->sample_size, buffer, size, offset); + if (flags & TR_SAMPLE_FLAGS) + prop_copy_uint32 (entry->sample_flags, buffer, size, offset); + if (flags & TR_COMPOSITION_TIME_OFFSETS) + prop_copy_uint32 (entry->sample_composition_time_offset, + buffer, size, offset); + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_traf_copy_data (AtomTRAF * traf, guint8 ** buffer, guint64 * size, + guint64 * offset, guint32 * data_offset) +{ + guint64 original_offset = *offset; + GList *walker; + + if (!atom_copy_data (&traf->header, buffer, size, offset)) { + return 0; + } + if (!atom_tfhd_copy_data (&traf->tfhd, buffer, size, offset)) { + return 0; + } + + walker = g_list_first (traf->truns); + while (walker != NULL) { + if (!atom_trun_copy_data ((AtomTRUN *) walker->data, buffer, size, offset, + data_offset)) { + return 0; + } + walker = g_list_next (walker); + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +/* creates moof atom; metadata is written expecting actual buffer data + * is in mdata directly after moof, and is consecutively written per trak */ +guint64 +atom_moof_copy_data (AtomMOOF * moof, guint8 ** buffer, + guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + GList *walker; + guint32 data_offset = 0; + + if (!atom_copy_data (&moof->header, buffer, size, offset)) + return 0; + + if (!atom_mfhd_copy_data (&moof->mfhd, buffer, size, offset)) + return 0; + + walker = g_list_first (moof->trafs); + while (walker != NULL) { + if (!atom_traf_copy_data ((AtomTRAF *) walker->data, buffer, size, offset, + &data_offset)) { + return 0; + } + walker = g_list_next (walker); + } + + atom_write_size (buffer, size, offset, original_offset); + + if (*buffer && data_offset) { + /* first trun needs a data-offset relative to moof start + * = moof size + mdat prefix */ + GST_WRITE_UINT32_BE (*buffer + data_offset, *offset - original_offset + 8); + } + + return *offset - original_offset; +} + +static void +atom_tfhd_init (AtomTFHD * tfhd, guint32 track_ID) +{ + guint8 flags[3] = { 0, 0, 0 }; + + atom_full_init (&tfhd->header, FOURCC_tfhd, 0, 0, 0, flags); + tfhd->track_ID = track_ID; + tfhd->base_data_offset = 0; + tfhd->sample_description_index = 1; + tfhd->default_sample_duration = 0; + tfhd->default_sample_size = 0; + tfhd->default_sample_flags = 0; +} + +static void +atom_trun_init (AtomTRUN * trun) +{ + guint8 flags[3] = { 0, 0, 0 }; + + atom_full_init (&trun->header, FOURCC_trun, 0, 0, 0, flags); + trun->sample_count = 0; + trun->data_offset = 0; + trun->first_sample_flags = 0; + atom_array_init (&trun->entries, 512); +} + +static AtomTRUN * +atom_trun_new (void) +{ + AtomTRUN *trun = g_new0 (AtomTRUN, 1); + + atom_trun_init (trun); + return trun; +} + +static void +atom_trun_add_samples (AtomTRUN * trun, guint32 delta, guint32 size, + guint32 flags, gboolean do_pts, gint64 pts_offset) +{ + TRUNSampleEntry nentry; + + if (do_pts) { + trun->header.flags[1] |= TR_COMPOSITION_TIME_OFFSETS; + } + + nentry.sample_duration = delta; + nentry.sample_size = size; + nentry.sample_flags = flags; + nentry.sample_composition_time_offset = do_pts ? pts_offset : 0; + atom_array_append (&trun->entries, nentry, 256); + trun->sample_count++; +} + +static void +atom_traf_init (AtomTRAF * traf, AtomsContext * context, guint32 track_ID) +{ + atom_header_set (&traf->header, FOURCC_traf, 0, 0); + atom_tfhd_init (&traf->tfhd, track_ID); + traf->truns = NULL; +} + +AtomTRAF * +atom_traf_new (AtomsContext * context, guint32 track_ID) +{ + AtomTRAF *traf = g_new0 (AtomTRAF, 1); + + atom_traf_init (traf, context, track_ID); + return traf; +} + +static void +atom_traf_add_trun (AtomTRAF * traf, AtomTRUN * trun) +{ + traf->truns = g_list_append (traf->truns, trun); +} + +void +atom_traf_add_samples (AtomTRAF * traf, guint32 delta, guint32 size, + gboolean sync, gboolean do_pts, gint64 pts_offset) +{ + AtomTRUN *trun; + guint32 flags; + + /* 0x10000 is sample-is-difference-sample flag + * low byte stuff is what ismv uses */ + flags = sync ? 0x0040 : 0x1000c; + + if (G_UNLIKELY (!traf->truns)) { + trun = atom_trun_new (); + atom_traf_add_trun (traf, trun); + /* optimistic; indicate all defaults present in tfhd */ + traf->tfhd.header.flags[2] = TF_DEFAULT_SAMPLE_DURATION | + TF_DEFAULT_SAMPLE_SIZE | TF_DEFAULT_SAMPLE_FLAGS; + traf->tfhd.default_sample_duration = delta; + traf->tfhd.default_sample_size = size; + traf->tfhd.default_sample_flags = flags; + trun->first_sample_flags = flags; + } + + trun = traf->truns->data; + + /* check if still matching defaults, + * if not, abandon default and need entry for each sample */ + if (traf->tfhd.default_sample_duration != delta) { + traf->tfhd.header.flags[2] &= ~TF_DEFAULT_SAMPLE_DURATION; + trun->header.flags[1] |= (TR_SAMPLE_DURATION >> 8); + } + if (traf->tfhd.default_sample_size != size) { + traf->tfhd.header.flags[2] &= ~TF_DEFAULT_SAMPLE_SIZE; + trun->header.flags[1] |= (TR_SAMPLE_SIZE >> 8); + } + if (traf->tfhd.default_sample_flags != flags) { + if (trun->sample_count == 1) { + /* at least will need first sample flag */ + traf->tfhd.default_sample_flags = flags; + trun->header.flags[2] |= TR_FIRST_SAMPLE_FLAGS; + } else { + /* now we need sample flags for each sample */ + traf->tfhd.header.flags[2] &= ~TF_DEFAULT_SAMPLE_FLAGS; + trun->header.flags[1] |= (TR_SAMPLE_FLAGS >> 8); + trun->header.flags[2] &= ~TR_FIRST_SAMPLE_FLAGS; + } + } + + atom_trun_add_samples (traf->truns->data, delta, size, flags, do_pts, + pts_offset); +} + +void +atom_moof_add_traf (AtomMOOF * moof, AtomTRAF * traf) +{ + moof->trafs = g_list_append (moof->trafs, traf); +} + /* some sample description construction helpers */ AtomInfo * diff --git a/gst/quicktime/atoms.h b/gst/quicktime/atoms.h index f48b263666..980c8973de 100644 --- a/gst/quicktime/atoms.h +++ b/gst/quicktime/atoms.h @@ -582,6 +582,26 @@ typedef struct _AtomUDTA AtomMETA *meta; } AtomUDTA; +enum TrFlags +{ + TR_DATA_OFFSET = 0x01, /* data-offset-present */ + TR_FIRST_SAMPLE_FLAGS = 0x04, /* first-sample-flags-present */ + TR_SAMPLE_DURATION = 0x0100, /* sample-duration-present */ + TR_SAMPLE_SIZE = 0x0200, /* sample-size-present */ + TR_SAMPLE_FLAGS = 0x0400, /* sample-flags-present */ + TR_COMPOSITION_TIME_OFFSETS = 0x0800 /* sample-composition-time-offsets-presents */ +}; + +enum TfFlags +{ + TF_BASE_DATA_OFFSET = 0x01, /* base-data-offset-present */ + TF_SAMPLE_DESCRIPTION_INDEX = 0x02, /* sample-description-index-present */ + TF_DEFAULT_SAMPLE_DURATION = 0x08, /* default-sample-duration-present */ + TF_DEFAULT_SAMPLE_SIZE = 0x010, /* default-sample-size-present */ + TF_DEFAULT_SAMPLE_FLAGS = 0x020, /* default-sample-flags-present */ + TF_DURATION_IS_EMPTY = 0x010000 /* sample-composition-time-offsets-presents */ +}; + typedef struct _AtomTRAK { Atom header; @@ -624,6 +644,66 @@ typedef struct _AtomMVEX GList *trexs; } AtomMVEX; +typedef struct _AtomMFHD +{ + AtomFull header; + + guint32 sequence_number; +} AtomMFHD; + +typedef struct _AtomTFHD +{ + AtomFull header; + + guint32 track_ID; + guint64 base_data_offset; + guint32 sample_description_index; + guint32 default_sample_duration; + guint32 default_sample_size; + guint32 default_sample_flags; +} AtomTFHD; + +typedef struct _TRUNSampleEntry +{ + guint32 sample_duration; + guint32 sample_size; + guint32 sample_flags; + guint32 sample_composition_time_offset; +} TRUNSampleEntry; + +typedef struct _AtomTRUN +{ + AtomFull header; + + guint32 sample_count; + gint32 data_offset; + guint32 first_sample_flags; + + /* array of fields */ + ATOM_ARRAY (TRUNSampleEntry) entries; +} AtomTRUN; + +typedef struct _AtomTRAF +{ + Atom header; + + AtomTFHD tfhd; + + /* list of AtomTRUN */ + GList *truns; +} AtomTRAF; + +typedef struct _AtomMOOF +{ + Atom header; + + AtomMFHD mfhd; + + /* list of AtomTRAF */ + GList *trafs; +} AtomMOOF; + + typedef struct _AtomMOOV { /* style */ @@ -689,6 +769,7 @@ void atom_trak_add_samples (AtomTRAK * trak, guint32 nsamples, guint void atom_trak_add_elst_entry (AtomTRAK * trak, guint32 duration, guint32 media_time, guint32 rate); guint32 atom_trak_get_timescale (AtomTRAK *trak); +guint32 atom_trak_get_id (AtomTRAK * trak); void atom_stbl_add_samples (AtomSTBL * stbl, guint32 nsamples, guint32 delta, guint32 size, guint64 chunk_offset, gboolean sync, @@ -723,6 +804,15 @@ guint64 atom_ctts_copy_data (AtomCTTS *atom, guint8 **buffer, guint64 *size, guint64* offset); guint64 atom_stco64_copy_data (AtomSTCO64 *atom, guint8 **buffer, guint64 *size, guint64* offset); +AtomMOOF* atom_moof_new (AtomsContext *context, guint32 sequence_number); +void atom_moof_free (AtomMOOF *moof); +guint64 atom_moof_copy_data (AtomMOOF *moof, guint8 **buffer, guint64 *size, guint64* offset); +AtomTRAF * atom_traf_new (AtomsContext * context, guint32 track_ID); +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); +void atom_moof_add_traf (AtomMOOF *moof, AtomTRAF *traf); /* media sample description related helpers */ diff --git a/gst/quicktime/gstqtmux.c b/gst/quicktime/gstqtmux.c index e2e1e5239e..e00d25b5fd 100644 --- a/gst/quicktime/gstqtmux.c +++ b/gst/quicktime/gstqtmux.c @@ -109,7 +109,8 @@ enum PROP_FLAVOR, PROP_FAST_START, PROP_FAST_START_TEMP_FILE, - PROP_MOOV_RECOV_FILE + PROP_MOOV_RECOV_FILE, + PROP_FRAGMENT_DURATION }; /* some spare for header size as well */ @@ -122,6 +123,8 @@ enum #define DEFAULT_FAST_START FALSE #define DEFAULT_FAST_START_TEMP_FILE NULL #define DEFAULT_MOOV_RECOV_FILE NULL +#define DEFAULT_FRAGMENT_DURATION 0 + static void gst_qt_mux_finalize (GObject * object); @@ -243,6 +246,11 @@ gst_qt_mux_class_init (GstQTMuxClass * klass) "of a crash during muxing. Null for disabled. (Experimental)", DEFAULT_MOOV_RECOV_FILE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_FRAGMENT_DURATION, + g_param_spec_uint ("fragment-duration", "Fragment duration", + "Fragment durations in ms (produce a fragmented file if > 0)", + 0, G_MAXUINT32, DEFAULT_FRAGMENT_DURATION, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); gstelement_class->request_new_pad = GST_DEBUG_FUNCPTR (gst_qt_mux_request_new_pad); @@ -269,6 +277,12 @@ gst_qt_mux_pad_reset (GstQTPad * qtpad) /* reference owned elsewhere */ qtpad->trak = NULL; + + if (qtpad->traf) { + atom_traf_free (qtpad->traf); + qtpad->traf = NULL; + } + atom_array_clear (&qtpad->fragment_buffers); } /* @@ -286,6 +300,7 @@ gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc) qtmux->longest_chunk = GST_CLOCK_TIME_NONE; qtmux->video_pads = 0; qtmux->audio_pads = 0; + qtmux->fragment_sequence = 0; if (qtmux->ftyp) { atom_ftyp_free (qtmux->ftyp); @@ -1324,14 +1339,13 @@ gst_qt_mux_prepare_and_send_ftyp (GstQTMux * qtmux) static void gst_qt_mux_configure_moov (GstQTMux * qtmux, guint32 * _timescale) { - gboolean large_file; + gboolean large_file, fragmented; guint32 timescale; - g_return_if_fail (_timescale != NULL); - GST_OBJECT_LOCK (qtmux); - *_timescale = timescale = qtmux->timescale; + timescale = qtmux->timescale; large_file = qtmux->large_file; + fragmented = qtmux->fragment_sequence > 0; GST_OBJECT_UNLOCK (qtmux); /* inform lower layers of our property wishes, and determine duration. @@ -1342,8 +1356,12 @@ gst_qt_mux_configure_moov (GstQTMux * qtmux, guint32 * _timescale) timescale); atom_moov_update_timescale (qtmux->moov, timescale); atom_moov_set_64bits (qtmux->moov, large_file); + atom_moov_set_fragmented (qtmux->moov, fragmented); atom_moov_update_duration (qtmux->moov); + + if (_timescale) + *_timescale = timescale; } static GstFlowReturn @@ -1503,9 +1521,28 @@ gst_qt_mux_start_file (GstQTMux * qtmux) goto exit; } - /* extended to ensure some spare space */ + /* well, it's moov pos if fragmented ... */ qtmux->mdat_pos = qtmux->header_size; - ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0, TRUE); + + if (qtmux->fragment_duration) { + GST_DEBUG_OBJECT (qtmux, "fragment duration %d ms, writing headers", + qtmux->fragment_duration); + /* also used as snapshot marker to indicate fragmented file */ + qtmux->fragment_sequence = 1; + /* 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); + if (ret != GST_FLOW_OK) + return ret; + /* extra atoms */ + ret = gst_qt_mux_send_extra_atoms (qtmux, TRUE, NULL, FALSE); + if (ret != GST_FLOW_OK) + return ret; + } else { + /* extended to ensure some spare space */ + ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0, TRUE); + } } exit: @@ -1547,11 +1584,37 @@ gst_qt_mux_stop_file (GstQTMux * qtmux) GST_WARNING_OBJECT (qtmux, "Failed to send last buffer for %s, " "flow return: %s", GST_PAD_NAME (qtpad->collect.pad), gst_flow_get_name (ret)); + /* determine max stream duration */ + if (!GST_CLOCK_TIME_IS_VALID (first_ts) || + (GST_CLOCK_TIME_IS_VALID (qtpad->first_ts) && + qtpad->last_dts > first_ts)) { + first_ts = qtpad->last_dts; + } + } + + if (qtmux->fragment_sequence) { + GstEvent *event; + + timescale = qtmux->timescale; + /* only mvex duration is updated, + * mvhd should be consistent with empty moov + * (but TODO maybe some clients do not handle that well ?) */ + qtmux->moov->mvex.mehd.fragment_duration = + gst_util_uint64_scale (first_ts, timescale, GST_SECOND); + GST_DEBUG_OBJECT (qtmux, "rewriting moov with mvex duration %" + GST_TIME_FORMAT, GST_TIME_ARGS (first_ts)); + /* seek and rewrite the header */ + event = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, + qtmux->mdat_pos, GST_CLOCK_TIME_NONE, 0); + gst_pad_push_event (qtmux->srcpad, event); + /* no need to seek back */ + return gst_qt_mux_send_moov (qtmux, NULL, FALSE); } gst_qt_mux_configure_moov (qtmux, ×cale); /* check for late streams */ + first_ts = GST_CLOCK_TIME_NONE; for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) { GstCollectData *cdata = (GstCollectData *) walk->data; GstQTPad *qtpad = (GstQTPad *) cdata; @@ -1667,6 +1730,82 @@ 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) +{ + GstFlowReturn ret = GST_FLOW_OK; + + /* setup if needed */ + if (G_UNLIKELY (!pad->traf || force)) + goto init; + +flush: + /* flush pad fragment if threshold reached, + * or at new keyframe if we should be minding those in the first place */ + if (G_UNLIKELY (force || (sync && pad->sync) || + pad->fragment_duration < (gint64) delta)) { + AtomMOOF *moof; + guint64 size = 0, offset = 0; + guint8 *data = NULL; + GstBuffer *buffer; + guint i, total_size; + + moof = atom_moof_new (qtmux->context, qtmux->fragment_sequence); + /* takes ownership */ + atom_moof_add_traf (moof, pad->traf); + pad->traf = NULL; + 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); + + /* and actual data */ + total_size = 0; + for (i = 0; i < atom_array_get_len (&pad->fragment_buffers); i++) { + total_size += + GST_BUFFER_SIZE (atom_array_index (&pad->fragment_buffers, i)); + } + + 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); + 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); + else + gst_buffer_unref (atom_array_index (&pad->fragment_buffers, i)); + } + + atom_array_clear (&pad->fragment_buffers); + atom_moof_free (moof); + qtmux->fragment_sequence++; + force = FALSE; + } + +init: + if (G_UNLIKELY (!pad->traf)) { + GST_LOG_OBJECT (qtmux, "setting up new fragment"); + pad->traf = atom_traf_new (qtmux->context, atom_trak_get_id (pad->trak)); + atom_array_init (&pad->fragment_buffers, 512); + pad->fragment_duration = gst_util_uint64_scale (qtmux->fragment_duration, + atom_trak_get_timescale (pad->trak), 1000); + } + + /* add buffer and metadata */ + atom_traf_add_samples (pad->traf, delta, size, sync, do_pts, pts_offset); + atom_array_append (&pad->fragment_buffers, buf, 256); + pad->fragment_duration -= delta; + + if (G_UNLIKELY (force)) + goto flush; + + return ret; +} + /* check whether @a differs from @b by order of @magn */ static gboolean inline gst_qtmux_check_difference (GstQTMux * qtmux, GstClockTime a, @@ -1782,7 +1921,8 @@ gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf) last_dts = gst_util_uint64_scale_round (pad->last_dts, atom_trak_get_timescale (pad->trak), GST_SECOND); - if (pad->sample_size) { + /* fragments only deal with 1 buffer == 1 chunk (== 1 sample) */ + if (pad->sample_size && !qtmux->fragment_sequence) { /* Constant size packets: usually raw audio (with many samples per buffer (= chunk)), but can also be fixed-packet-size codecs like ADPCM */ @@ -1902,13 +2042,20 @@ gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf) qtmux->moov_recov_file = NULL; } } - atom_trak_add_samples (pad->trak, nsamples, scaled_duration, sample_size, - chunk_offset, sync, do_pts, pts_offset); if (buf) gst_buffer_unref (buf); - return gst_qt_mux_send_buffer (qtmux, last_buf, &qtmux->mdat_size, TRUE); + 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); + } else { + atom_trak_add_samples (pad->trak, nsamples, scaled_duration, sample_size, + chunk_offset, sync, do_pts, pts_offset); + return gst_qt_mux_send_buffer (qtmux, last_buf, &qtmux->mdat_size, TRUE); + } /* ERRORS */ bail: @@ -2829,6 +2976,9 @@ gst_qt_mux_get_property (GObject * object, case PROP_MOOV_RECOV_FILE: g_value_set_string (value, qtmux->moov_recov_file_path); break; + case PROP_FRAGMENT_DURATION: + g_value_set_uint (value, qtmux->fragment_duration); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -2881,6 +3031,9 @@ gst_qt_mux_set_property (GObject * object, g_free (qtmux->moov_recov_file_path); qtmux->moov_recov_file_path = g_value_dup_string (value); break; + case PROP_FRAGMENT_DURATION: + qtmux->fragment_duration = g_value_get_uint (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; diff --git a/gst/quicktime/gstqtmux.h b/gst/quicktime/gstqtmux.h index 013211a930..9856d77814 100644 --- a/gst/quicktime/gstqtmux.h +++ b/gst/quicktime/gstqtmux.h @@ -106,6 +106,13 @@ struct _GstQTPad /* all the atom and chunk book-keeping is delegated here * unowned/uncounted reference, parent MOOV owns */ AtomTRAK *trak; + /* fragmented support */ + /* meta data book-keeping delegated here */ + AtomTRAF *traf; + /* fragment buffers */ + ATOM_ARRAY (GstBuffer *) fragment_buffers; + /* running fragment duration */ + gint64 fragment_duration; /* if nothing is set, it won't be called */ GstQTPadPrepareBufferFunc prepare_buf_func; @@ -153,6 +160,9 @@ struct _GstQTMux /* moov recovery */ FILE *moov_recov_file; + /* fragment sequence */ + guint32 fragment_sequence; + /* properties */ guint32 timescale; AtomsTreeFlavor flavor; @@ -161,6 +171,7 @@ struct _GstQTMux gboolean guess_pts; gchar *fast_start_file_path; gchar *moov_recov_file_path; + guint32 fragment_duration; /* for collect pads event handling function */ GstPadEventFunction collect_event;