qtmux: optionally create fragmented file

In this mode, an initial empty moov (containing only stream metadata) is written,
followed by fragments containing actual data (along with required metadata).
New fragments are started either at keyframe (if such are sparse) or when
property configured duration exceeded.

Based on patch by Marc-André Lureau <mlureau@flumotion.com>

Fixes #632911.
This commit is contained in:
Mark Nauwelaerts 2010-11-15 15:17:59 +01:00 committed by Tim-Philipp Müller
parent c61dc2ec4a
commit 4b64dc2f3e
4 changed files with 637 additions and 11 deletions

View file

@ -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 *

View file

@ -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 */

View file

@ -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, &timescale);
/* 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;

View file

@ -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;