mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-28 03:00:35 +00:00
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:
parent
c61dc2ec4a
commit
4b64dc2f3e
4 changed files with 637 additions and 11 deletions
|
@ -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 *
|
||||
|
|
|
@ -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 */
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue