mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-10 17:35:59 +00:00
isomp4/mux: add a fragment mode for initial moov with data
Used by some proprietary software for their fragmented files. Adds some support for multi-stream fragmented files Flow is as follows. 1. The first 'fragment' is written as a self-contained fragmented mdat+moov complete with an edit list and durations, tags, etc. 2. Subsequent fragments are written with a mdat+moof and each stream is interleaved as data arrives (currently ignoring the interleave-* properties). data-offsets in both the traf and the trun ensure data is read from the correct place on demuxing. Data/chunk offsets are also kept for writing out the final moov. 3. On finalisation, the initial moov is invalidated to a hoov and the size of the first mdat is extended to cover the entire file contents. Then a moov is written as regularly would in moov-at-end mode (the default). This results in a file that is playable throughout while leaving a finalised file on completion for players that do not understand fragmented mp4. Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/merge_requests/643>
This commit is contained in:
parent
97e932d500
commit
52b63de19a
7 changed files with 872 additions and 179 deletions
|
@ -9188,6 +9188,21 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"GstQTMuxFragmentMode": {
|
||||
"kind": "enum",
|
||||
"values": [
|
||||
{
|
||||
"desc": "dash-or-mss",
|
||||
"name": "Dash or Smoothstreaming",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"desc": "first-moov-then-finalise",
|
||||
"name": "First MOOV Fragment Then Finalise",
|
||||
"value": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"GstQTMuxPad": {
|
||||
"hierarchy": [
|
||||
"GstQTMuxPad",
|
||||
|
|
|
@ -2953,8 +2953,11 @@ atom_mvex_copy_data (AtomMVEX * mvex, guint8 ** buffer, guint64 * size,
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (!atom_mehd_copy_data (&mvex->mehd, buffer, size, offset)) {
|
||||
return 0;
|
||||
if (mvex->mehd.fragment_duration > 0) {
|
||||
/* only write mehd if we have anything extra to add */
|
||||
if (!atom_mehd_copy_data (&mvex->mehd, buffer, size, offset)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
walker = g_list_first (mvex->trexs);
|
||||
|
@ -4457,6 +4460,25 @@ atom_moof_new (AtomsContext * context, guint32 sequence_number)
|
|||
return moof;
|
||||
}
|
||||
|
||||
void
|
||||
atom_moof_set_base_offset (AtomMOOF * moof, guint64 offset)
|
||||
{
|
||||
GList *trafs = moof->trafs;
|
||||
|
||||
if (offset == moof->traf_offset)
|
||||
return; /* Nothing to do */
|
||||
|
||||
while (trafs) {
|
||||
AtomTRAF *traf = (AtomTRAF *) trafs->data;
|
||||
|
||||
traf->tfhd.header.flags[2] |= TF_BASE_DATA_OFFSET;
|
||||
traf->tfhd.base_data_offset = offset;
|
||||
trafs = g_list_next (trafs);
|
||||
}
|
||||
|
||||
moof->traf_offset = offset;
|
||||
}
|
||||
|
||||
static void
|
||||
atom_trun_free (AtomTRUN * trun)
|
||||
{
|
||||
|
@ -4581,21 +4603,13 @@ atom_tfdt_copy_data (AtomTFDT * tfdt, guint8 ** buffer, guint64 * size,
|
|||
|
||||
static guint64
|
||||
atom_trun_copy_data (AtomTRUN * trun, guint8 ** buffer, guint64 * size,
|
||||
guint64 * offset, guint32 * data_offset)
|
||||
guint64 * 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)) {
|
||||
|
@ -4605,7 +4619,6 @@ atom_trun_copy_data (AtomTRUN * trun, guint8 ** buffer, guint64 * size,
|
|||
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)
|
||||
|
@ -4649,7 +4662,7 @@ atom_sdtp_copy_data (AtomSDTP * sdtp, guint8 ** buffer, guint64 * size,
|
|||
|
||||
static guint64
|
||||
atom_traf_copy_data (AtomTRAF * traf, guint8 ** buffer, guint64 * size,
|
||||
guint64 * offset, guint32 * data_offset)
|
||||
guint64 * offset)
|
||||
{
|
||||
guint64 original_offset = *offset;
|
||||
GList *walker;
|
||||
|
@ -4663,11 +4676,9 @@ atom_traf_copy_data (AtomTRAF * traf, guint8 ** buffer, guint64 * size,
|
|||
if (!atom_tfdt_copy_data (&traf->tfdt, 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)) {
|
||||
if (!atom_trun_copy_data ((AtomTRUN *) walker->data, buffer, size, offset)) {
|
||||
return 0;
|
||||
}
|
||||
walker = g_list_next (walker);
|
||||
|
@ -4693,7 +4704,6 @@ atom_moof_copy_data (AtomMOOF * moof, guint8 ** buffer,
|
|||
{
|
||||
guint64 original_offset = *offset;
|
||||
GList *walker;
|
||||
guint32 data_offset = 0;
|
||||
|
||||
if (!atom_copy_data (&moof->header, buffer, size, offset))
|
||||
return 0;
|
||||
|
@ -4703,8 +4713,7 @@ atom_moof_copy_data (AtomMOOF * moof, guint8 ** buffer,
|
|||
|
||||
walker = g_list_first (moof->trafs);
|
||||
while (walker != NULL) {
|
||||
if (!atom_traf_copy_data ((AtomTRAF *) walker->data, buffer, size, offset,
|
||||
&data_offset)) {
|
||||
if (!atom_traf_copy_data ((AtomTRAF *) walker->data, buffer, size, offset)) {
|
||||
return 0;
|
||||
}
|
||||
walker = g_list_next (walker);
|
||||
|
@ -4712,12 +4721,6 @@ atom_moof_copy_data (AtomMOOF * moof, guint8 ** buffer,
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -4797,21 +4800,62 @@ atom_sdtp_add_samples (AtomSDTP * sdtp, guint8 val)
|
|||
atom_array_append (&sdtp->entries, val, 256);
|
||||
}
|
||||
|
||||
static void
|
||||
atom_trun_add_samples (AtomTRUN * trun, guint32 delta, guint32 size,
|
||||
guint32 flags, gint64 pts_offset)
|
||||
void
|
||||
atom_trun_set_offset (AtomTRUN * trun, gint32 offset)
|
||||
{
|
||||
TRUNSampleEntry nentry;
|
||||
trun->header.flags[2] |= TR_DATA_OFFSET;
|
||||
trun->data_offset = offset;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
atom_trun_can_append_samples_to_entry (AtomTRUN * trun,
|
||||
TRUNSampleEntry * nentry, guint32 nsamples, guint32 delta, guint32 size,
|
||||
guint32 flags, gint32 data_offset, gint64 pts_offset)
|
||||
{
|
||||
if (pts_offset != 0)
|
||||
return FALSE;
|
||||
if (nentry->sample_flags != flags)
|
||||
return FALSE;
|
||||
if (trun->data_offset + nentry->sample_size != data_offset)
|
||||
return FALSE;
|
||||
if (nentry->sample_size != size)
|
||||
return FALSE;
|
||||
if (nentry->sample_duration != delta)
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
atom_trun_append_samples (AtomTRUN * trun, TRUNSampleEntry * nentry,
|
||||
guint32 nsamples, guint32 delta, guint32 size)
|
||||
{
|
||||
trun->sample_count += nsamples;
|
||||
}
|
||||
|
||||
static void
|
||||
atom_trun_add_samples (AtomTRUN * trun, guint32 nsamples, guint32 delta,
|
||||
guint32 size, guint32 flags, gint64 pts_offset)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (pts_offset != 0)
|
||||
trun->header.flags[1] |= (TR_COMPOSITION_TIME_OFFSETS >> 8);
|
||||
|
||||
nentry.sample_duration = delta;
|
||||
nentry.sample_size = size;
|
||||
nentry.sample_flags = flags;
|
||||
nentry.sample_composition_time_offset = pts_offset;
|
||||
atom_array_append (&trun->entries, nentry, 256);
|
||||
trun->sample_count++;
|
||||
for (i = 0; i < nsamples; i++) {
|
||||
TRUNSampleEntry nentry;
|
||||
|
||||
nentry.sample_duration = delta;
|
||||
nentry.sample_size = size;
|
||||
nentry.sample_flags = flags;
|
||||
if (pts_offset != 0) {
|
||||
nentry.sample_composition_time_offset = pts_offset + i * delta;
|
||||
} else {
|
||||
nentry.sample_composition_time_offset = 0;
|
||||
}
|
||||
atom_array_append (&trun->entries, nentry, 256);
|
||||
trun->sample_count++;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -4851,41 +4895,67 @@ atom_traf_add_trun (AtomTRAF * traf, AtomTRUN * trun)
|
|||
}
|
||||
|
||||
void
|
||||
atom_traf_add_samples (AtomTRAF * traf, guint32 delta, guint32 size,
|
||||
gboolean sync, gint64 pts_offset, gboolean sdtp_sync)
|
||||
atom_traf_add_samples (AtomTRAF * traf, guint32 nsamples,
|
||||
guint32 delta, guint32 size, gint32 data_offset, gboolean sync,
|
||||
gint64 pts_offset, gboolean sdtp_sync)
|
||||
{
|
||||
AtomTRUN *trun;
|
||||
GList *l = NULL;
|
||||
AtomTRUN *prev_trun, *trun = NULL;
|
||||
TRUNSampleEntry *nentry = NULL;
|
||||
guint32 flags;
|
||||
|
||||
/* 0x10000 is sample-is-difference-sample flag
|
||||
* low byte stuff is what ismv uses */
|
||||
flags = (sync ? 0x0 : 0x10000) | (sdtp_sync ? 0x40 : 0xc0);
|
||||
|
||||
if (G_UNLIKELY (!traf->truns)) {
|
||||
trun = atom_trun_new ();
|
||||
atom_traf_add_trun (traf, trun);
|
||||
if (traf->truns) {
|
||||
trun = g_list_last (traf->truns)->data;
|
||||
nentry =
|
||||
&atom_array_index (&trun->entries,
|
||||
atom_array_get_len (&trun->entries) - 1);
|
||||
|
||||
if (!atom_trun_can_append_samples_to_entry (trun, nentry, nsamples, delta,
|
||||
size, flags, data_offset, pts_offset)) {
|
||||
/* if we can't add to the previous trun, write a new one */
|
||||
trun = NULL;
|
||||
nentry = NULL;
|
||||
}
|
||||
}
|
||||
prev_trun = trun;
|
||||
|
||||
if (!traf->truns) {
|
||||
/* 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;
|
||||
if (!trun) {
|
||||
trun = atom_trun_new ();
|
||||
atom_traf_add_trun (traf, trun);
|
||||
trun->first_sample_flags = flags;
|
||||
trun->data_offset = data_offset;
|
||||
if (data_offset != 0)
|
||||
trun->header.flags[2] |= TR_DATA_OFFSET;
|
||||
}
|
||||
|
||||
/* check if still matching defaults,
|
||||
* if not, abandon default and need entry for each sample */
|
||||
if (traf->tfhd.default_sample_duration != delta) {
|
||||
if (traf->tfhd.default_sample_duration != delta || prev_trun == trun) {
|
||||
traf->tfhd.header.flags[2] &= ~TF_DEFAULT_SAMPLE_DURATION;
|
||||
trun->header.flags[1] |= (TR_SAMPLE_DURATION >> 8);
|
||||
for (l = traf->truns; l; l = g_list_next (l)) {
|
||||
((AtomTRUN *) l->data)->header.flags[1] |= (TR_SAMPLE_DURATION >> 8);
|
||||
}
|
||||
}
|
||||
if (traf->tfhd.default_sample_size != size) {
|
||||
if (traf->tfhd.default_sample_size != size || prev_trun == trun) {
|
||||
traf->tfhd.header.flags[2] &= ~TF_DEFAULT_SAMPLE_SIZE;
|
||||
trun->header.flags[1] |= (TR_SAMPLE_SIZE >> 8);
|
||||
for (l = traf->truns; l; l = g_list_next (l)) {
|
||||
((AtomTRUN *) l->data)->header.flags[1] |= (TR_SAMPLE_SIZE >> 8);
|
||||
}
|
||||
}
|
||||
if (traf->tfhd.default_sample_flags != flags) {
|
||||
if (traf->tfhd.default_sample_flags != flags || prev_trun == trun) {
|
||||
if (trun->sample_count == 1) {
|
||||
/* at least will need first sample flag */
|
||||
traf->tfhd.default_sample_flags = flags;
|
||||
|
@ -4898,7 +4968,11 @@ atom_traf_add_samples (AtomTRAF * traf, guint32 delta, guint32 size,
|
|||
}
|
||||
}
|
||||
|
||||
atom_trun_add_samples (traf->truns->data, delta, size, flags, pts_offset);
|
||||
if (prev_trun == trun) {
|
||||
atom_trun_append_samples (trun, nentry, nsamples, delta, size);
|
||||
} else {
|
||||
atom_trun_add_samples (trun, nsamples, delta, size, flags, pts_offset);
|
||||
}
|
||||
|
||||
if (traf->sdtps)
|
||||
atom_sdtp_add_samples (traf->sdtps->data, 0x10 | ((flags & 0xff) >> 4));
|
||||
|
@ -4912,6 +4986,7 @@ atom_traf_get_sample_num (AtomTRAF * traf)
|
|||
if (G_UNLIKELY (!traf->truns))
|
||||
return 0;
|
||||
|
||||
/* FIXME: only one trun? */
|
||||
trun = traf->truns->data;
|
||||
return atom_array_get_len (&trun->entries);
|
||||
}
|
||||
|
|
|
@ -831,7 +831,7 @@ typedef struct _AtomTRAF
|
|||
|
||||
AtomTFDT tfdt;
|
||||
|
||||
/* list of AtomTRUN */
|
||||
/* list of AtomTRUN. */
|
||||
GList *truns;
|
||||
/* list of AtomSDTP */
|
||||
GList *sdtps;
|
||||
|
@ -845,6 +845,8 @@ typedef struct _AtomMOOF
|
|||
|
||||
/* list of AtomTRAF */
|
||||
GList *trafs;
|
||||
|
||||
guint64 traf_offset;
|
||||
} AtomMOOF;
|
||||
|
||||
|
||||
|
@ -989,13 +991,15 @@ guint64 atom_stco64_copy_data (AtomSTCO64 *atom, guint8 **buffer,
|
|||
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);
|
||||
void atom_moof_set_base_offset (AtomMOOF * moof, guint64 offset);
|
||||
AtomTRAF * atom_traf_new (AtomsContext * context, guint32 track_ID);
|
||||
void atom_traf_free (AtomTRAF * traf);
|
||||
void atom_traf_set_base_decode_time (AtomTRAF * traf, guint64 base_decode_time);
|
||||
void atom_traf_add_samples (AtomTRAF * traf, guint32 delta,
|
||||
guint32 size, gboolean sync, gint64 pts_offset,
|
||||
gboolean sdtp_sync);
|
||||
void atom_traf_add_samples (AtomTRAF * traf, guint32 nsamples, guint32 delta,
|
||||
guint32 size, gint32 data_offset, gboolean sync,
|
||||
gint64 pts_offset, gboolean sdtp_sync);
|
||||
guint32 atom_traf_get_sample_num (AtomTRAF * traf);
|
||||
void atom_trun_set_offset (AtomTRUN * trun, gint32 offset);
|
||||
void atom_moof_add_traf (AtomMOOF *moof, AtomTRAF *traf);
|
||||
|
||||
AtomMFRA* atom_mfra_new (AtomsContext *context);
|
||||
|
|
|
@ -259,6 +259,33 @@ gst_qt_mux_dts_method_get_type (void)
|
|||
(gst_qt_mux_dts_method_get_type ())
|
||||
#endif
|
||||
|
||||
static GType
|
||||
gst_qt_mux_fragment_mode_get_type (void)
|
||||
{
|
||||
static GType gst_qt_mux_fragment_mode = 0;
|
||||
|
||||
if (!gst_qt_mux_fragment_mode) {
|
||||
static const GEnumValue gst_qt_mux_fragment_modes[] = {
|
||||
{GST_QT_MUX_FRAGMENT_DASH_OR_MSS, "Dash or Smoothstreaming",
|
||||
"dash-or-mss"},
|
||||
{GST_QT_MUX_FRAGMENT_FIRST_MOOV_THEN_FINALISE,
|
||||
"First MOOV Fragment Then Finalise", "first-moov-then-finalise"},
|
||||
/* internal only */
|
||||
/* {GST_QT_MUX_FRAGMENT_STREAMABLE, "streamable", "Streamable (ISML only. Deprecated elsewhere)"}, */
|
||||
{0, NULL, NULL},
|
||||
};
|
||||
|
||||
gst_qt_mux_fragment_mode =
|
||||
g_enum_register_static ("GstQTMuxFragmentMode",
|
||||
gst_qt_mux_fragment_modes);
|
||||
}
|
||||
|
||||
return gst_qt_mux_fragment_mode;
|
||||
}
|
||||
|
||||
#define GST_TYPE_QT_MUX_FRAGMENT_MODE \
|
||||
(gst_qt_mux_fragment_mode_get_type ())
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_PAD_0,
|
||||
|
@ -370,6 +397,7 @@ enum
|
|||
PROP_MAX_RAW_AUDIO_DRIFT,
|
||||
PROP_START_GAP_THRESHOLD,
|
||||
PROP_FORCE_CREATE_TIMECODE_TRAK,
|
||||
PROP_FRAGMENT_MODE,
|
||||
};
|
||||
|
||||
/* some spare for header size as well */
|
||||
|
@ -396,6 +424,7 @@ enum
|
|||
#define DEFAULT_MAX_RAW_AUDIO_DRIFT 40 * GST_MSECOND
|
||||
#define DEFAULT_START_GAP_THRESHOLD 0
|
||||
#define DEFAULT_FORCE_CREATE_TIMECODE_TRAK FALSE
|
||||
#define DEFAULT_FRAGMENT_MODE GST_QT_MUX_FRAGMENT_DASH_OR_MSS
|
||||
|
||||
static void gst_qt_mux_finalize (GObject * object);
|
||||
|
||||
|
@ -440,7 +469,7 @@ static void gst_qt_mux_update_edit_lists (GstQTMux * qtmux);
|
|||
static GstElementClass *parent_class = NULL;
|
||||
|
||||
static void
|
||||
gst_qt_mux_base_init (gpointer g_class)
|
||||
gst_qt_mux_subclass_base_init (gpointer g_class)
|
||||
{
|
||||
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
|
||||
GstQTMuxClass *klass = (GstQTMuxClass *) g_class;
|
||||
|
@ -502,7 +531,7 @@ gst_qt_mux_base_init (gpointer g_class)
|
|||
}
|
||||
|
||||
static void
|
||||
gst_qt_mux_class_init (GstQTMuxClass * klass)
|
||||
gst_qt_mux_subclass_class_init (GstQTMuxClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class;
|
||||
GstElementClass *gstelement_class;
|
||||
|
@ -651,6 +680,29 @@ gst_qt_mux_class_init (GstQTMuxClass * klass)
|
|||
DEFAULT_FORCE_CREATE_TIMECODE_TRAK,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
/**
|
||||
* GstQTMux:fragment-mode:
|
||||
*
|
||||
* Influence how fragmented files are produces. Only has any affect when the
|
||||
* the 'fragment-duration' property is set to a value greater than '0'
|
||||
*
|
||||
* Currently, two options exist:
|
||||
* - "dash-or-mss": for the original fragmented mode that supports dash or
|
||||
* mocrosoft smoothstreaming with a single input stream
|
||||
* - "first-moov-then-finalise" is a fragmented mode that will start with a
|
||||
* self-contained 'moov' atom fo the first fragment, then produce fragments.
|
||||
* When the file is finalised, the initial 'moov' is invalidated and a
|
||||
* new 'moov' is written covering the entire file.
|
||||
*
|
||||
* Since: 1.20
|
||||
*/
|
||||
g_object_class_install_property (gobject_class, PROP_FRAGMENT_MODE,
|
||||
g_param_spec_enum ("fragment-mode", "Fragment Mode",
|
||||
"How to to write fragments to the file. Only used when "
|
||||
"\'fragment-duration\' is greather than 0",
|
||||
GST_TYPE_QT_MUX_FRAGMENT_MODE, DEFAULT_FRAGMENT_MODE,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
gstelement_class->request_new_pad =
|
||||
GST_DEBUG_FUNCPTR (gst_qt_mux_request_new_pad);
|
||||
gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_qt_mux_release_pad);
|
||||
|
@ -665,6 +717,7 @@ gst_qt_mux_class_init (GstQTMuxClass * klass)
|
|||
|
||||
gst_type_mark_as_plugin_api (GST_TYPE_QT_MUX_PAD, 0);
|
||||
gst_type_mark_as_plugin_api (GST_TYPE_QT_MUX_DTS_METHOD, 0);
|
||||
gst_type_mark_as_plugin_api (GST_TYPE_QT_MUX_FRAGMENT_MODE, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -861,7 +914,7 @@ gst_qt_mux_clip_running_time (GstAggregator * agg,
|
|||
}
|
||||
|
||||
static void
|
||||
gst_qt_mux_init (GstQTMux * qtmux, GstQTMuxClass * qtmux_klass)
|
||||
gst_qt_mux_subclass_init (GstQTMux * qtmux, GstQTMuxClass * qtmux_klass)
|
||||
{
|
||||
/* properties set to default upon construction */
|
||||
|
||||
|
@ -2283,18 +2336,9 @@ serialize_error:
|
|||
}
|
||||
|
||||
static void
|
||||
gst_qt_mux_configure_moov (GstQTMux * qtmux)
|
||||
gst_qt_mux_configure_moov_full (GstQTMux * qtmux, gboolean fragmented,
|
||||
guint32 timescale)
|
||||
{
|
||||
gboolean fragmented = FALSE;
|
||||
guint32 timescale;
|
||||
|
||||
GST_OBJECT_LOCK (qtmux);
|
||||
timescale = qtmux->timescale;
|
||||
if (qtmux->mux_mode == GST_QT_MUX_MODE_FRAGMENTED ||
|
||||
qtmux->mux_mode == GST_QT_MUX_MODE_FRAGMENTED_STREAMABLE)
|
||||
fragmented = TRUE;
|
||||
GST_OBJECT_UNLOCK (qtmux);
|
||||
|
||||
/* inform lower layers of our property wishes, and determine duration.
|
||||
* Let moov take care of this using its list of traks;
|
||||
* so that released pads are also included */
|
||||
|
@ -2306,6 +2350,22 @@ gst_qt_mux_configure_moov (GstQTMux * qtmux)
|
|||
atom_moov_update_duration (qtmux->moov);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_qt_mux_configure_moov (GstQTMux * qtmux)
|
||||
{
|
||||
gboolean fragmented = FALSE;
|
||||
guint32 timescale;
|
||||
|
||||
GST_OBJECT_LOCK (qtmux);
|
||||
timescale = qtmux->timescale;
|
||||
if (qtmux->mux_mode == GST_QT_MUX_MODE_FRAGMENTED
|
||||
&& qtmux->fragment_mode != GST_QT_MUX_FRAGMENT_FIRST_MOOV_THEN_FINALISE)
|
||||
fragmented = TRUE;
|
||||
GST_OBJECT_UNLOCK (qtmux);
|
||||
|
||||
gst_qt_mux_configure_moov_full (qtmux, fragmented, timescale);
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_qt_mux_send_moov (GstQTMux * qtmux, guint64 * _offset,
|
||||
guint64 padded_moov_size, gboolean mind_fast, gboolean fsync_after)
|
||||
|
@ -2813,8 +2873,7 @@ find_best_pad_prefill_start (GstQTMux * qtmux)
|
|||
|| qtmux->current_chunk_size <= qtmux->interleave_bytes)
|
||||
&& (qtmux->interleave_time == 0
|
||||
|| qtmux->current_chunk_duration <= qtmux->interleave_time)
|
||||
&& qtmux->mux_mode != GST_QT_MUX_MODE_FRAGMENTED
|
||||
&& qtmux->mux_mode != GST_QT_MUX_MODE_FRAGMENTED_STREAMABLE) {
|
||||
&& qtmux->mux_mode != GST_QT_MUX_MODE_FRAGMENTED) {
|
||||
|
||||
if (qtmux->current_pad->total_duration < qtmux->reserved_max_duration) {
|
||||
best_pad = qtmux->current_pad;
|
||||
|
@ -3041,10 +3100,11 @@ gst_qt_mux_start_file (GstQTMux * qtmux)
|
|||
goto invalid_isml;
|
||||
|
||||
if (qtmux->fragment_duration > 0) {
|
||||
if (qtmux->streamable)
|
||||
qtmux->mux_mode = GST_QT_MUX_MODE_FRAGMENTED_STREAMABLE;
|
||||
else
|
||||
qtmux->mux_mode = GST_QT_MUX_MODE_FRAGMENTED;
|
||||
qtmux->mux_mode = GST_QT_MUX_MODE_FRAGMENTED;
|
||||
if (qtmux->streamable
|
||||
&& qtmux->fragment_mode == GST_QT_MUX_FRAGMENT_DASH_OR_MSS) {
|
||||
qtmux->fragment_mode = GST_QT_MUX_FRAGMENT_STREAMABLE;
|
||||
}
|
||||
} else if (qtmux->fast_start) {
|
||||
qtmux->mux_mode = GST_QT_MUX_MODE_FAST_START;
|
||||
} else if (reserved_max_duration != GST_CLOCK_TIME_NONE) {
|
||||
|
@ -3078,9 +3138,10 @@ gst_qt_mux_start_file (GstQTMux * qtmux)
|
|||
}
|
||||
break;
|
||||
case GST_QT_MUX_MODE_FAST_START:
|
||||
case GST_QT_MUX_MODE_FRAGMENTED_STREAMABLE:
|
||||
break; /* Don't need seekability, ignore */
|
||||
case GST_QT_MUX_MODE_FRAGMENTED:
|
||||
if (qtmux->fragment_mode == GST_QT_MUX_FRAGMENT_STREAMABLE)
|
||||
break;
|
||||
if (!gst_qt_mux_downstream_is_seekable (qtmux)) {
|
||||
GST_WARNING_OBJECT (qtmux, "downstream is not seekable, but "
|
||||
"streamable=false. Will ignore that and create streamable output "
|
||||
|
@ -3408,31 +3469,46 @@ gst_qt_mux_start_file (GstQTMux * qtmux)
|
|||
ret = gst_qt_mux_send_buffer (qtmux, gst_buffer_new (), NULL, FALSE);
|
||||
break;
|
||||
case GST_QT_MUX_MODE_FRAGMENTED:
|
||||
case GST_QT_MUX_MODE_FRAGMENTED_STREAMABLE:
|
||||
ret = gst_qt_mux_prepare_and_send_ftyp (qtmux);
|
||||
if (ret != GST_FLOW_OK)
|
||||
break;
|
||||
/* store the moov pos so we can update the duration later
|
||||
* in non-streamable mode */
|
||||
qtmux->moov_pos = qtmux->header_size;
|
||||
|
||||
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);
|
||||
gst_qt_mux_setup_metadata (qtmux);
|
||||
ret = gst_qt_mux_send_moov (qtmux, &qtmux->header_size, 0, FALSE, FALSE);
|
||||
if (ret != GST_FLOW_OK)
|
||||
return ret;
|
||||
/* extra atoms */
|
||||
ret =
|
||||
gst_qt_mux_send_extra_atoms (qtmux, TRUE, &qtmux->header_size, FALSE);
|
||||
if (ret != GST_FLOW_OK)
|
||||
break;
|
||||
/* prepare index if not streamable */
|
||||
if (qtmux->mux_mode == GST_QT_MUX_MODE_FRAGMENTED)
|
||||
qtmux->fragment_sequence = 0;
|
||||
if (qtmux->fragment_mode == GST_QT_MUX_FRAGMENT_FIRST_MOOV_THEN_FINALISE) {
|
||||
/* Store this as the mdat offset for later updating
|
||||
* when we write the moov */
|
||||
qtmux->mdat_pos = qtmux->header_size;
|
||||
/* extended atom in case we go over 4GB while writing and need
|
||||
* the full 64-bit atom */
|
||||
ret =
|
||||
gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0, TRUE,
|
||||
FALSE);
|
||||
if (ret != GST_FLOW_OK)
|
||||
return ret;
|
||||
} else {
|
||||
/* store the moov pos so we can update the duration later
|
||||
* in non-streamable mode */
|
||||
qtmux->moov_pos = qtmux->header_size;
|
||||
|
||||
/* prepare moov and/or tags */
|
||||
qtmux->fragment_sequence++;
|
||||
gst_qt_mux_configure_moov (qtmux);
|
||||
gst_qt_mux_setup_metadata (qtmux);
|
||||
ret =
|
||||
gst_qt_mux_send_moov (qtmux, &qtmux->header_size, 0, FALSE, FALSE);
|
||||
if (ret != GST_FLOW_OK)
|
||||
return ret;
|
||||
/* extra atoms */
|
||||
ret =
|
||||
gst_qt_mux_send_extra_atoms (qtmux, TRUE, &qtmux->header_size,
|
||||
FALSE);
|
||||
if (ret != GST_FLOW_OK)
|
||||
break;
|
||||
}
|
||||
/* prepare index if not streamable, or overwriting with moov */
|
||||
if (qtmux->fragment_mode == GST_QT_MUX_FRAGMENT_DASH_OR_MSS)
|
||||
qtmux->mfra = atom_mfra_new (qtmux->context);
|
||||
break;
|
||||
}
|
||||
|
@ -3718,7 +3794,8 @@ gst_qt_mux_stop_file (GstQTMux * qtmux)
|
|||
if ((ret = gst_qt_mux_send_last_buffers (qtmux)) != GST_FLOW_OK)
|
||||
return ret;
|
||||
|
||||
if (qtmux->mux_mode == GST_QT_MUX_MODE_FRAGMENTED_STREAMABLE) {
|
||||
if (qtmux->mux_mode == GST_QT_MUX_MODE_FRAGMENTED
|
||||
&& qtmux->fragment_mode == GST_QT_MUX_FRAGMENT_STREAMABLE) {
|
||||
/* Streamable mode; no need to write duration or MFRA */
|
||||
GST_DEBUG_OBJECT (qtmux, "streamable file; nothing to stop");
|
||||
return GST_FLOW_OK;
|
||||
|
@ -3750,33 +3827,81 @@ gst_qt_mux_stop_file (GstQTMux * qtmux)
|
|||
switch (qtmux->mux_mode) {
|
||||
case GST_QT_MUX_MODE_FRAGMENTED:{
|
||||
GstSegment segment;
|
||||
guint8 *data = NULL;
|
||||
GstBuffer *buf;
|
||||
GstClockTime duration;
|
||||
|
||||
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;
|
||||
if (qtmux->mfra) {
|
||||
guint8 *data = NULL;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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_round (qtmux->last_dts, qtmux->timescale,
|
||||
duration = gst_util_uint64_scale_round (qtmux->last_dts, qtmux->timescale,
|
||||
GST_SECOND);
|
||||
|
||||
GST_DEBUG_OBJECT (qtmux,
|
||||
"rewriting moov with mvex duration %" GST_TIME_FORMAT,
|
||||
"writing moov with mvhd/mvex duration %" GST_TIME_FORMAT,
|
||||
GST_TIME_ARGS (qtmux->last_dts));
|
||||
/* seek and rewrite the header */
|
||||
gst_segment_init (&segment, GST_FORMAT_BYTES);
|
||||
segment.start = qtmux->moov_pos;
|
||||
gst_aggregator_update_segment (GST_AGGREGATOR (qtmux), &segment);
|
||||
/* no need to seek back */
|
||||
return gst_qt_mux_send_moov (qtmux, NULL, 0, FALSE, FALSE);
|
||||
if (qtmux->fragment_mode == GST_QT_MUX_FRAGMENT_FIRST_MOOV_THEN_FINALISE) {
|
||||
/* seek and overwrite the original moov with an invalid atom */
|
||||
/* XXX: assumes an extended size atom is not used for the moov */
|
||||
|
||||
qtmux->moov->mvhd.time_info.duration = duration;
|
||||
|
||||
gst_segment_init (&segment, GST_FORMAT_BYTES);
|
||||
segment.start = qtmux->moov_pos + 4; /* skip the size bytes */
|
||||
gst_aggregator_update_segment (GST_AGGREGATOR (qtmux), &segment);
|
||||
|
||||
/* invalidate the previous moov */
|
||||
buf = gst_buffer_new_wrapped (g_strdup ("h"), 1);
|
||||
ret = gst_qt_mux_send_buffer (qtmux, buf, NULL, FALSE);
|
||||
if (ret != GST_FLOW_OK)
|
||||
return ret;
|
||||
|
||||
/* we want to rewrite the first mdat to cover the entire data before
|
||||
* this moov */
|
||||
qtmux->mdat_size = qtmux->header_size - qtmux->mdat_pos - 16;
|
||||
|
||||
gst_segment_init (&segment, GST_FORMAT_BYTES);
|
||||
segment.start = qtmux->mdat_pos;
|
||||
gst_aggregator_update_segment (GST_AGGREGATOR (qtmux), &segment);
|
||||
|
||||
ret = gst_qt_mux_update_mdat_size (qtmux, qtmux->mdat_pos,
|
||||
qtmux->mdat_size, NULL, FALSE);
|
||||
if (ret != GST_FLOW_OK)
|
||||
return ret;
|
||||
|
||||
/* Then write the moov atom as in moov-at-end *without* updating the
|
||||
* mdat size */
|
||||
gst_segment_init (&segment, GST_FORMAT_BYTES);
|
||||
segment.start = qtmux->header_size;
|
||||
gst_aggregator_update_segment (GST_AGGREGATOR (qtmux), &segment);
|
||||
|
||||
/* revert back to moov-at-end assumptions where header_size is the
|
||||
* size up to the first byte of data in the mdat */
|
||||
qtmux->header_size = qtmux->mdat_pos + 16;
|
||||
break;
|
||||
} else {
|
||||
qtmux->moov->mvex.mehd.fragment_duration = duration;
|
||||
|
||||
/* seek and rewrite the header */
|
||||
gst_segment_init (&segment, GST_FORMAT_BYTES);
|
||||
segment.start = qtmux->moov_pos;
|
||||
gst_aggregator_update_segment (GST_AGGREGATOR (qtmux), &segment);
|
||||
/* no need to seek back */
|
||||
return gst_qt_mux_send_moov (qtmux, NULL, 0, FALSE, FALSE);
|
||||
}
|
||||
}
|
||||
case GST_QT_MUX_MODE_ROBUST_RECORDING:{
|
||||
ret = gst_qt_mux_robust_recording_rewrite_moov (qtmux);
|
||||
|
@ -4054,6 +4179,10 @@ gst_qt_mux_stop_file (GstQTMux * qtmux)
|
|||
return ret;
|
||||
break;
|
||||
}
|
||||
case GST_QT_MUX_MODE_FRAGMENTED:
|
||||
g_assert (qtmux->fragment_mode ==
|
||||
GST_QT_MUX_FRAGMENT_FIRST_MOOV_THEN_FINALISE);
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
@ -4074,14 +4203,48 @@ ftyp_error:
|
|||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_qtmux_pad_update_fragment_duration (GstElement * element, GstPad * pad,
|
||||
gpointer user_data)
|
||||
{
|
||||
GstQTMux *qtmux = (GstQTMux *) element;
|
||||
GstQTMuxPad *qt_pad = GST_QT_MUX_PAD (pad);
|
||||
|
||||
qt_pad->fragment_duration = gst_util_uint64_scale (qtmux->fragment_duration,
|
||||
atom_trak_get_timescale (qt_pad->trak), 1000);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_qtmux_pad_collect_traf (GstElement * element, GstPad * pad,
|
||||
gpointer user_data)
|
||||
{
|
||||
GstQTMuxPad *qt_pad = GST_QT_MUX_PAD (pad);
|
||||
AtomMOOF *moof = user_data;
|
||||
|
||||
GST_TRACE_OBJECT (pad, "adding traf %p to moof %p", qt_pad->traf, moof);
|
||||
|
||||
/* takes ownership */
|
||||
if (qt_pad->traf)
|
||||
atom_moof_add_traf (moof, qt_pad->traf);
|
||||
qt_pad->traf = NULL;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_qt_mux_pad_fragment_add_buffer (GstQTMux * qtmux, GstQTMuxPad * pad,
|
||||
GstBuffer * buf, gboolean force, guint32 nsamples, gint64 dts,
|
||||
guint32 delta, guint32 size, gboolean sync, gint64 pts_offset)
|
||||
guint32 delta, guint32 size, guint64 chunk_offset, gboolean sync,
|
||||
gint64 pts_offset)
|
||||
{
|
||||
GstFlowReturn ret = GST_FLOW_OK;
|
||||
guint index = 0;
|
||||
|
||||
GST_LOG_OBJECT (pad, "%p %u %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT,
|
||||
pad->traf, force, qtmux->current_chunk_offset, chunk_offset);
|
||||
|
||||
/* setup if needed */
|
||||
if (G_UNLIKELY (!pad->traf || force))
|
||||
goto init;
|
||||
|
@ -4091,65 +4254,230 @@ flush:
|
|||
* 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;
|
||||
|
||||
/* now we know where moof ends up, update offset in tfra */
|
||||
if (pad->tfra)
|
||||
atom_tfra_update_offset (pad->tfra, qtmux->header_size);
|
||||
if (qtmux->fragment_mode == GST_QT_MUX_FRAGMENT_FIRST_MOOV_THEN_FINALISE) {
|
||||
if (qtmux->fragment_sequence == 0) {
|
||||
/* the first fragment which we write as a moov */
|
||||
GstSegment segment;
|
||||
guint64 orig_offset;
|
||||
guint64 offset = orig_offset = qtmux->mdat_pos + 16 + qtmux->mdat_size;
|
||||
guint64 chunk_increase;
|
||||
AtomMOOF *moof;
|
||||
|
||||
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 %" G_GSIZE_FORMAT,
|
||||
gst_buffer_get_size (buffer));
|
||||
ret = gst_qt_mux_send_buffer (qtmux, buffer, &qtmux->header_size, FALSE);
|
||||
GST_LOG_OBJECT (qtmux, "current file offset calculated to be %"
|
||||
G_GUINT64_FORMAT " based on mdat pos %" G_GUINT64_FORMAT
|
||||
" and size %" G_GUINT64_FORMAT, offset, qtmux->mdat_pos,
|
||||
qtmux->mdat_size);
|
||||
|
||||
atom_moof_free (moof);
|
||||
moof = atom_moof_new (qtmux->context, qtmux->fragment_sequence);
|
||||
gst_element_foreach_sink_pad (GST_ELEMENT (qtmux),
|
||||
gst_qtmux_pad_collect_traf, moof);
|
||||
atom_moof_free (moof);
|
||||
|
||||
if (ret != GST_FLOW_OK)
|
||||
goto moof_send_error;
|
||||
ret = gst_qt_mux_update_mdat_size (qtmux, qtmux->mdat_pos,
|
||||
qtmux->mdat_size, NULL, FALSE);
|
||||
if (ret != GST_FLOW_OK)
|
||||
return ret;
|
||||
|
||||
/* and actual data */
|
||||
total_size = 0;
|
||||
for (i = 0; i < atom_array_get_len (&pad->fragment_buffers); i++) {
|
||||
total_size +=
|
||||
gst_buffer_get_size (atom_array_index (&pad->fragment_buffers, i));
|
||||
}
|
||||
/* seek back to the end of the file */
|
||||
gst_segment_init (&segment, GST_FORMAT_BYTES);
|
||||
segment.start = qtmux->moov_pos = offset;
|
||||
gst_aggregator_update_segment (GST_AGGREGATOR (qtmux), &segment);
|
||||
|
||||
GST_LOG_OBJECT (qtmux, "writing %d buffers, total_size %d",
|
||||
atom_array_get_len (&pad->fragment_buffers), total_size);
|
||||
ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, total_size,
|
||||
FALSE, FALSE);
|
||||
if (ret != GST_FLOW_OK)
|
||||
goto mdat_header_send_error;
|
||||
/* update moov data */
|
||||
gst_qt_mux_update_global_statistics (qtmux);
|
||||
gst_qt_mux_configure_moov_full (qtmux, TRUE, qtmux->timescale);
|
||||
gst_qt_mux_update_edit_lists (qtmux);
|
||||
gst_qt_mux_setup_metadata (qtmux);
|
||||
/* chunk offset is the offset to the first byte inside the mdat */
|
||||
atom_moov_chunks_set_offset (qtmux->moov, qtmux->mdat_pos + 16);
|
||||
|
||||
for (index = 0; index < atom_array_get_len (&pad->fragment_buffers);
|
||||
index++) {
|
||||
GST_DEBUG_OBJECT (qtmux, "sending fragment %p",
|
||||
atom_array_index (&pad->fragment_buffers, index));
|
||||
ret = gst_qt_mux_send_moov (qtmux, &offset, 0, TRUE, FALSE);
|
||||
if (ret != GST_FLOW_OK)
|
||||
return ret;
|
||||
|
||||
/* for the continuation in fragments, header_size is the tracking write
|
||||
* position */
|
||||
qtmux->header_size = offset;
|
||||
qtmux->moof_mdat_pos = 0;
|
||||
|
||||
chunk_increase = offset - orig_offset + 16;
|
||||
GST_LOG_OBJECT (qtmux, "We think we have written %" G_GUINT64_FORMAT
|
||||
" including a moov and mdat header of %" G_GUINT64_FORMAT
|
||||
". mangling this buffer's chunk offset from %" G_GUINT64_FORMAT
|
||||
" to %" G_GUINT64_FORMAT, qtmux->header_size,
|
||||
offset - orig_offset + 16, chunk_offset,
|
||||
chunk_offset + chunk_increase);
|
||||
chunk_offset += chunk_increase;
|
||||
/* this is the offset for the next chunk */
|
||||
qtmux->current_chunk_offset +=
|
||||
qtmux->current_chunk_size + chunk_increase;
|
||||
qtmux->current_chunk_size = 0;
|
||||
GST_LOG_OBJECT (qtmux, "change next chunk offset to %" G_GUINT64_FORMAT
|
||||
" and size to %" G_GUINT64_FORMAT, qtmux->current_chunk_offset,
|
||||
qtmux->current_chunk_size);
|
||||
|
||||
gst_element_foreach_sink_pad (GST_ELEMENT (qtmux),
|
||||
gst_qtmux_pad_update_fragment_duration, NULL);
|
||||
} else {
|
||||
AtomMOOF *moof;
|
||||
guint64 size = 0, offset = 0;
|
||||
guint8 *data = NULL;
|
||||
GstBuffer *moof_buffer;
|
||||
guint64 moof_size = 0;
|
||||
GstSegment segment;
|
||||
guint64 chunk_increase;
|
||||
|
||||
/* rewrite the mdat header */
|
||||
ret = gst_qt_mux_update_mdat_size (qtmux, qtmux->moof_mdat_pos,
|
||||
qtmux->header_size - qtmux->moof_mdat_pos - 16, NULL, FALSE);
|
||||
if (ret != GST_FLOW_OK)
|
||||
return ret;
|
||||
|
||||
/* reseek back to the current position */
|
||||
gst_segment_init (&segment, GST_FORMAT_BYTES);
|
||||
segment.start = qtmux->header_size;
|
||||
gst_aggregator_update_segment (GST_AGGREGATOR (qtmux), &segment);
|
||||
|
||||
moof = atom_moof_new (qtmux->context, qtmux->fragment_sequence);
|
||||
gst_element_foreach_sink_pad (GST_ELEMENT (qtmux),
|
||||
gst_qtmux_pad_collect_traf, moof);
|
||||
atom_moof_set_base_offset (moof, qtmux->moof_mdat_pos);
|
||||
atom_moof_copy_data (moof, &data, &size, &offset);
|
||||
moof_buffer = _gst_buffer_new_take_data (data, offset);
|
||||
moof_size = gst_buffer_get_size (moof_buffer);
|
||||
|
||||
atom_moof_free (moof);
|
||||
/* now we know where moof ends up, update offset in tfra */
|
||||
if (pad->tfra)
|
||||
atom_tfra_update_offset (pad->tfra, qtmux->header_size);
|
||||
|
||||
GST_LOG_OBJECT (qtmux, "writing moof of size %" G_GUINT64_FORMAT,
|
||||
moof_size);
|
||||
ret =
|
||||
gst_qt_mux_send_buffer (qtmux, moof_buffer, &qtmux->header_size,
|
||||
FALSE);
|
||||
if (ret != GST_FLOW_OK)
|
||||
goto moof_send_error;
|
||||
qtmux->moof_mdat_pos = 0;
|
||||
|
||||
/* if we are writing a final moov, then we need to increase our chunk
|
||||
* offsets to include the moof/mdat headers that were just written so
|
||||
* so that they are correctly skipped over.
|
||||
*/
|
||||
chunk_increase = moof_size + 16;
|
||||
GST_LOG_OBJECT (qtmux, "We think we have currently written %"
|
||||
G_GUINT64_FORMAT " including a moof of %" G_GUINT64_FORMAT
|
||||
" mangling this buffer's chunk offset from %" G_GUINT64_FORMAT
|
||||
" to %" G_GUINT64_FORMAT, qtmux->header_size, moof_size,
|
||||
chunk_offset, chunk_offset + chunk_increase);
|
||||
chunk_offset += chunk_increase;
|
||||
/* this is the offset for the next chunk */
|
||||
qtmux->current_chunk_offset +=
|
||||
qtmux->current_chunk_size + chunk_increase;
|
||||
qtmux->current_chunk_size = 0;
|
||||
GST_LOG_OBJECT (qtmux, "change next chunk offset to %" G_GUINT64_FORMAT
|
||||
" and size to %" G_GUINT64_FORMAT, qtmux->current_chunk_offset,
|
||||
qtmux->current_chunk_size);
|
||||
|
||||
/* if we are are generating a moof, it is for all streams */
|
||||
gst_element_foreach_sink_pad (GST_ELEMENT (qtmux),
|
||||
gst_qtmux_pad_update_fragment_duration, NULL);
|
||||
}
|
||||
} else {
|
||||
/* not moov-related. writes out moof then mdat for a single stream only */
|
||||
AtomMOOF *moof;
|
||||
guint64 size = 0, offset = 0;
|
||||
guint8 *data = NULL;
|
||||
GstBuffer *moof_buffer;
|
||||
guint i, total_size;
|
||||
AtomTRUN *first_trun;
|
||||
|
||||
total_size = 0;
|
||||
for (i = 0; i < atom_array_get_len (&pad->fragment_buffers); i++) {
|
||||
total_size +=
|
||||
gst_buffer_get_size (atom_array_index (&pad->fragment_buffers, i));
|
||||
}
|
||||
|
||||
moof = atom_moof_new (qtmux->context, qtmux->fragment_sequence);
|
||||
/* takes ownership */
|
||||
atom_moof_add_traf (moof, pad->traf);
|
||||
/* write the offset into the first 'trun'. All other truns are assumed
|
||||
* to follow on from this trun. skip over the mdat header (+8) */
|
||||
atom_moof_copy_data (moof, &data, &size, &offset);
|
||||
first_trun = (AtomTRUN *) pad->traf->truns->data;
|
||||
atom_trun_set_offset (first_trun, size + 8);
|
||||
pad->traf = NULL;
|
||||
size = offset = 0;
|
||||
atom_moof_copy_data (moof, &data, &size, &offset);
|
||||
moof_buffer = _gst_buffer_new_take_data (data, offset);
|
||||
|
||||
atom_moof_free (moof);
|
||||
|
||||
/* now we know where moof ends up, update offset in tfra */
|
||||
if (pad->tfra)
|
||||
atom_tfra_update_offset (pad->tfra, qtmux->header_size);
|
||||
|
||||
GST_LOG_OBJECT (qtmux, "writing moof size %" G_GSIZE_FORMAT,
|
||||
gst_buffer_get_size (moof_buffer));
|
||||
ret =
|
||||
gst_qt_mux_send_buffer (qtmux,
|
||||
atom_array_index (&pad->fragment_buffers, index), &qtmux->header_size,
|
||||
gst_qt_mux_send_buffer (qtmux, moof_buffer, &qtmux->header_size,
|
||||
FALSE);
|
||||
if (ret != GST_FLOW_OK)
|
||||
goto fragment_buf_send_error;
|
||||
}
|
||||
goto moof_send_error;
|
||||
|
||||
GST_LOG_OBJECT (qtmux, "writing %d buffers, total_size %d",
|
||||
atom_array_get_len (&pad->fragment_buffers), total_size);
|
||||
|
||||
ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, total_size,
|
||||
FALSE, FALSE);
|
||||
if (ret != GST_FLOW_OK)
|
||||
goto mdat_header_send_error;
|
||||
|
||||
for (index = 0; index < atom_array_get_len (&pad->fragment_buffers);
|
||||
index++) {
|
||||
GST_DEBUG_OBJECT (qtmux, "sending fragment %p",
|
||||
atom_array_index (&pad->fragment_buffers, index));
|
||||
ret =
|
||||
gst_qt_mux_send_buffer (qtmux,
|
||||
atom_array_index (&pad->fragment_buffers, index),
|
||||
&qtmux->header_size, FALSE);
|
||||
if (ret != GST_FLOW_OK)
|
||||
goto fragment_buf_send_error;
|
||||
}
|
||||
|
||||
}
|
||||
atom_array_clear (&pad->fragment_buffers);
|
||||
qtmux->fragment_sequence++;
|
||||
force = FALSE;
|
||||
}
|
||||
|
||||
init:
|
||||
if (G_UNLIKELY (!pad->traf)) {
|
||||
GST_LOG_OBJECT (qtmux, "setting up new fragment");
|
||||
if (qtmux->fragment_mode == GST_QT_MUX_FRAGMENT_FIRST_MOOV_THEN_FINALISE
|
||||
&& qtmux->fragment_sequence == 0) {
|
||||
atom_trak_add_samples (pad->trak, nsamples, (gint32) delta, size,
|
||||
chunk_offset, sync, pts_offset);
|
||||
|
||||
ret = gst_qt_mux_send_buffer (qtmux, buf, &qtmux->mdat_size, TRUE);
|
||||
if (ret != GST_FLOW_OK)
|
||||
return ret;
|
||||
buf = NULL;
|
||||
|
||||
if (G_UNLIKELY (force))
|
||||
goto flush;
|
||||
|
||||
if (!pad->traf) {
|
||||
pad->traf = atom_traf_new (qtmux->context, atom_trak_get_id (pad->trak));
|
||||
pad->fragment_duration = gst_util_uint64_scale (qtmux->fragment_duration,
|
||||
atom_trak_get_timescale (pad->trak), 1000);
|
||||
}
|
||||
pad->fragment_duration -= delta;
|
||||
|
||||
return ret;
|
||||
} else if (G_UNLIKELY (!pad->traf)) {
|
||||
GstClockTime first_dts = 0, current_dts;
|
||||
gint64 first_qt_dts;
|
||||
GST_LOG_OBJECT (pad, "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,
|
||||
|
@ -4159,14 +4487,55 @@ init:
|
|||
pad->tfra = atom_tfra_new (qtmux->context, atom_trak_get_id (pad->trak));
|
||||
atom_mfra_add_tfra (qtmux->mfra, pad->tfra);
|
||||
}
|
||||
atom_traf_set_base_decode_time (pad->traf, dts);
|
||||
if (GST_CLOCK_TIME_IS_VALID (pad->first_dts))
|
||||
first_dts = pad->first_dts;
|
||||
|
||||
current_dts =
|
||||
gst_util_uint64_scale (dts, GST_SECOND,
|
||||
atom_trak_get_timescale (pad->trak));
|
||||
first_qt_dts =
|
||||
gst_util_uint64_scale (first_dts, atom_trak_get_timescale (pad->trak),
|
||||
GST_SECOND);
|
||||
GST_DEBUG_OBJECT (pad, "calculating base decode time with first dts %"
|
||||
G_GINT64_FORMAT " (%" GST_TIME_FORMAT ") and current dts %"
|
||||
G_GINT64_FORMAT " (%" GST_TIME_FORMAT ") of %" G_GINT64_FORMAT " (%"
|
||||
GST_STIME_FORMAT ")", first_qt_dts, GST_TIME_ARGS (first_dts), dts,
|
||||
GST_TIME_ARGS (current_dts), dts - first_qt_dts,
|
||||
GST_STIME_ARGS (current_dts - first_dts));
|
||||
atom_traf_set_base_decode_time (pad->traf, dts - first_qt_dts);
|
||||
}
|
||||
|
||||
/* add buffer and metadata */
|
||||
atom_traf_add_samples (pad->traf, delta, size, sync, pts_offset,
|
||||
pad->sync && sync);
|
||||
GST_LOG_OBJECT (qtmux, "adding buffer %p to fragments", buf);
|
||||
atom_array_append (&pad->fragment_buffers, g_steal_pointer (&buf), 256);
|
||||
if (qtmux->fragment_mode == GST_QT_MUX_FRAGMENT_FIRST_MOOV_THEN_FINALISE) {
|
||||
if (qtmux->fragment_sequence > 0 && !force) {
|
||||
if (qtmux->moof_mdat_pos == 0) {
|
||||
/* send temporary mdat */
|
||||
qtmux->moof_mdat_pos = qtmux->header_size;
|
||||
ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0,
|
||||
TRUE, FALSE);
|
||||
if (ret != GST_FLOW_OK)
|
||||
goto mdat_header_send_error;
|
||||
}
|
||||
|
||||
if (buf) {
|
||||
atom_trak_add_samples (pad->trak, nsamples, (gint32) delta, size,
|
||||
chunk_offset, sync, pts_offset);
|
||||
atom_traf_add_samples (pad->traf, nsamples, delta, size,
|
||||
qtmux->header_size - qtmux->moof_mdat_pos, sync, pts_offset,
|
||||
pad->sync && sync);
|
||||
|
||||
ret = gst_qt_mux_send_buffer (qtmux, buf, &qtmux->header_size, TRUE);
|
||||
if (ret != GST_FLOW_OK)
|
||||
return ret;
|
||||
buf = NULL;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* add buffer and metadata */
|
||||
atom_traf_add_samples (pad->traf, nsamples, delta, size, 0, sync,
|
||||
pts_offset, pad->sync && sync);
|
||||
GST_LOG_OBJECT (qtmux, "adding buffer %p to fragments", buf);
|
||||
atom_array_append (&pad->fragment_buffers, g_steal_pointer (&buf), 256);
|
||||
}
|
||||
pad->fragment_duration -= delta;
|
||||
|
||||
if (pad->tfra) {
|
||||
|
@ -4476,11 +4845,10 @@ gst_qt_mux_register_and_push_sample (GstQTMux * qtmux, GstQTMuxPad * pad,
|
|||
ret = gst_qt_mux_robust_recording_update (qtmux, pad->total_duration);
|
||||
break;
|
||||
case GST_QT_MUX_MODE_FRAGMENTED:
|
||||
case GST_QT_MUX_MODE_FRAGMENTED_STREAMABLE:
|
||||
/* 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);
|
||||
sample_size, chunk_offset, !pad->sync || sync, pts_offset);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -4500,6 +4868,7 @@ gst_qt_mux_register_buffer_in_chunk (GstQTMux * qtmux, GstQTMuxPad * pad,
|
|||
pad->total_duration += duration;
|
||||
/* for keeping track of where we are in chunk;
|
||||
* ensures that data really is located as recorded in atoms */
|
||||
|
||||
qtmux->current_chunk_size += buffer_size;
|
||||
qtmux->current_chunk_duration += duration;
|
||||
}
|
||||
|
@ -4533,6 +4902,8 @@ gst_qt_mux_check_and_update_timecode (GstQTMux * qtmux, GstQTMuxPad * pad,
|
|||
|
||||
/* This means we never got a timecode before */
|
||||
if (pad->first_tc == NULL) {
|
||||
guint64 *offset;
|
||||
|
||||
#ifndef GST_DISABLE_GST_DEBUG
|
||||
gchar *tc_str = gst_video_time_code_to_string (tc);
|
||||
GST_DEBUG_OBJECT (qtmux, "Found first timecode %s", tc_str);
|
||||
|
@ -4540,6 +4911,13 @@ gst_qt_mux_check_and_update_timecode (GstQTMux * qtmux, GstQTMuxPad * pad,
|
|||
#endif
|
||||
g_assert (pad->tc_trak == NULL);
|
||||
pad->first_tc = gst_video_time_code_copy (tc);
|
||||
|
||||
if (qtmux->mux_mode == GST_QT_MUX_MODE_FRAGMENTED
|
||||
&& qtmux->fragment_sequence > 0) {
|
||||
offset = &qtmux->header_size;
|
||||
} else {
|
||||
offset = &qtmux->mdat_size;
|
||||
}
|
||||
/* If frames are out of order, the frame we're currently getting might
|
||||
* not be the first one. Just write a 0 timecode for now and wait
|
||||
* until we receive a timecode that's lower than the current one */
|
||||
|
@ -4547,7 +4925,7 @@ gst_qt_mux_check_and_update_timecode (GstQTMux * qtmux, GstQTMuxPad * pad,
|
|||
pad->first_pts = GST_BUFFER_PTS (buf);
|
||||
frames_since_daily_jam = 0;
|
||||
/* Position to rewrite */
|
||||
pad->tc_pos = qtmux->mdat_size;
|
||||
pad->tc_pos = *offset;
|
||||
} else {
|
||||
frames_since_daily_jam =
|
||||
gst_video_time_code_frames_since_daily_jam (pad->first_tc);
|
||||
|
@ -4567,8 +4945,8 @@ gst_qt_mux_check_and_update_timecode (GstQTMux * qtmux, GstQTMuxPad * pad,
|
|||
szret = gst_buffer_fill (tc_buf, 0, &frames_since_daily_jam, 4);
|
||||
g_assert (szret == 4);
|
||||
|
||||
atom_trak_add_samples (pad->tc_trak, 1, 1, 4, qtmux->mdat_size, FALSE, 0);
|
||||
ret = gst_qt_mux_send_buffer (qtmux, tc_buf, &qtmux->mdat_size, TRUE);
|
||||
atom_trak_add_samples (pad->tc_trak, 1, 1, 4, *offset, FALSE, 0);
|
||||
ret = gst_qt_mux_send_buffer (qtmux, tc_buf, offset, TRUE);
|
||||
|
||||
/* Need to reset the current chunk (of the previous pad) here because
|
||||
* some other data was written now above, and the pad has to start a
|
||||
|
@ -4781,7 +5159,9 @@ gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTMuxPad * pad, GstBuffer * buf)
|
|||
atom_trak_get_timescale (pad->trak), GST_SECOND);
|
||||
|
||||
/* fragments only deal with 1 buffer == 1 chunk (== 1 sample) */
|
||||
if (pad->sample_size && !qtmux->fragment_sequence) {
|
||||
if (pad->sample_size && (qtmux->mux_mode != GST_QT_MUX_MODE_FRAGMENTED
|
||||
|| qtmux->fragment_mode ==
|
||||
GST_QT_MUX_FRAGMENT_FIRST_MOOV_THEN_FINALISE)) {
|
||||
GstClockTime expected_timestamp;
|
||||
|
||||
/* Constant size packets: usually raw audio (with many samples per
|
||||
|
@ -5151,8 +5531,7 @@ find_best_pad (GstQTMux * qtmux)
|
|||
|| qtmux->current_chunk_size <= qtmux->interleave_bytes)
|
||||
&& (qtmux->interleave_time == 0
|
||||
|| qtmux->current_chunk_duration <= qtmux->interleave_time)
|
||||
&& qtmux->mux_mode != GST_QT_MUX_MODE_FRAGMENTED
|
||||
&& qtmux->mux_mode != GST_QT_MUX_MODE_FRAGMENTED_STREAMABLE) {
|
||||
&& qtmux->mux_mode != GST_QT_MUX_MODE_FRAGMENTED) {
|
||||
GstBuffer *tmp_buf =
|
||||
gst_aggregator_pad_peek_buffer (GST_AGGREGATOR_PAD
|
||||
(qtmux->current_pad));
|
||||
|
@ -6686,6 +7065,13 @@ gst_qt_mux_get_property (GObject * object,
|
|||
case PROP_FORCE_CREATE_TIMECODE_TRAK:
|
||||
g_value_set_boolean (value, qtmux->force_create_timecode_trak);
|
||||
break;
|
||||
case PROP_FRAGMENT_MODE:{
|
||||
GstQTMuxFragmentMode mode = qtmux->fragment_mode;
|
||||
if (mode == GST_QT_MUX_FRAGMENT_STREAMABLE)
|
||||
mode = GST_QT_MUX_FRAGMENT_DASH_OR_MSS;
|
||||
g_value_set_enum (value, mode);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
|
@ -6788,6 +7174,12 @@ gst_qt_mux_set_property (GObject * object,
|
|||
qtmux->context->force_create_timecode_trak =
|
||||
qtmux->force_create_timecode_trak;
|
||||
break;
|
||||
case PROP_FRAGMENT_MODE:{
|
||||
GstQTMuxFragmentMode mode = g_value_get_enum (value);
|
||||
if (mode != GST_QT_MUX_FRAGMENT_STREAMABLE)
|
||||
qtmux->fragment_mode = mode;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
|
@ -6820,19 +7212,31 @@ gst_qt_mux_stop (GstAggregator * agg)
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
G_DEFINE_TYPE (GstQTMux, gst_qt_mux, GST_TYPE_AGGREGATOR);
|
||||
|
||||
static void
|
||||
gst_qt_mux_class_init (GstQTMuxClass * klass)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
gst_qt_mux_init (GstQTMux * qtmux)
|
||||
{
|
||||
}
|
||||
|
||||
gboolean
|
||||
gst_qt_mux_register (GstPlugin * plugin)
|
||||
{
|
||||
GTypeInfo typeinfo = {
|
||||
sizeof (GstQTMuxClass),
|
||||
(GBaseInitFunc) gst_qt_mux_base_init,
|
||||
(GBaseInitFunc) gst_qt_mux_subclass_base_init,
|
||||
NULL,
|
||||
(GClassInitFunc) gst_qt_mux_class_init,
|
||||
(GClassInitFunc) gst_qt_mux_subclass_class_init,
|
||||
NULL,
|
||||
NULL,
|
||||
sizeof (GstQTMux),
|
||||
0,
|
||||
(GInstanceInitFunc) gst_qt_mux_init,
|
||||
(GInstanceInitFunc) gst_qt_mux_subclass_init,
|
||||
};
|
||||
static const GInterfaceInfo tag_setter_info = {
|
||||
NULL, NULL, NULL
|
||||
|
@ -6882,8 +7286,8 @@ gst_qt_mux_register (GstPlugin * plugin)
|
|||
|
||||
/* create the type now */
|
||||
type =
|
||||
g_type_register_static (GST_TYPE_AGGREGATOR, prop->type_name, &typeinfo,
|
||||
0);
|
||||
g_type_register_static (gst_qt_mux_get_type (), prop->type_name,
|
||||
&typeinfo, 0);
|
||||
g_type_set_qdata (type, GST_QT_MUX_PARAMS_QDATA, (gpointer) params);
|
||||
g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, &tag_setter_info);
|
||||
g_type_add_interface_static (type, GST_TYPE_TAG_XMP_WRITER,
|
||||
|
|
|
@ -197,12 +197,26 @@ typedef enum _GstQTMuxState
|
|||
typedef enum _GstQtMuxMode {
|
||||
GST_QT_MUX_MODE_MOOV_AT_END,
|
||||
GST_QT_MUX_MODE_FRAGMENTED,
|
||||
GST_QT_MUX_MODE_FRAGMENTED_STREAMABLE,
|
||||
GST_QT_MUX_MODE_FAST_START,
|
||||
GST_QT_MUX_MODE_ROBUST_RECORDING,
|
||||
GST_QT_MUX_MODE_ROBUST_RECORDING_PREFILL,
|
||||
} GstQtMuxMode;
|
||||
|
||||
/**
|
||||
* GstQTMuxFragmentMode:
|
||||
* GST_QT_MUX_FRAGMENT_DASH_OR_MSS: dash-or-mss
|
||||
* GST_QT_MUX_FRAGMENT_FIRST_MOOV_THEN_FINALISE: first-moov-then-finalise
|
||||
* GST_QT_MUX_FRAGMENT_STREAMABLE: streamable (private value)
|
||||
*
|
||||
* Since: 1.20
|
||||
*/
|
||||
typedef enum _GstQTMuxFragmentMode
|
||||
{
|
||||
GST_QT_MUX_FRAGMENT_DASH_OR_MSS = 0,
|
||||
GST_QT_MUX_FRAGMENT_FIRST_MOOV_THEN_FINALISE,
|
||||
GST_QT_MUX_FRAGMENT_STREAMABLE = G_MAXUINT32, /* internal value */
|
||||
} GstQTMuxFragmentMode;
|
||||
|
||||
struct _GstQTMux
|
||||
{
|
||||
GstAggregator parent;
|
||||
|
@ -213,6 +227,9 @@ struct _GstQTMux
|
|||
/* Mux mode, inferred from property
|
||||
* set in gst_qt_mux_start_file() */
|
||||
GstQtMuxMode mux_mode;
|
||||
/* fragment_mode, controls how fragments are created. Only if
|
||||
* @mux_mode == GST_QT_MUX_MODE_FRAGMENTED */
|
||||
GstQTMuxFragmentMode fragment_mode;
|
||||
|
||||
/* size of header (prefix, atoms (ftyp, possibly moov, mdat header)) */
|
||||
guint64 header_size;
|
||||
|
@ -224,6 +241,10 @@ struct _GstQTMux
|
|||
/* position of mdat atom header (for later updating of size) in
|
||||
* moov-at-end, fragmented and robust-muxing modes */
|
||||
guint64 mdat_pos;
|
||||
/* position of the mdat atom header of the latest fragment for writing
|
||||
* the default base offset in fragmented mode first-moov-then-finalise and
|
||||
* any other future non-streaming fragmented mode */
|
||||
guint64 moof_mdat_pos;
|
||||
|
||||
/* keep track of the largest chunk to fine-tune brands */
|
||||
GstClockTime longest_chunk;
|
||||
|
@ -276,7 +297,7 @@ struct _GstQTMux
|
|||
guint32 fragment_duration;
|
||||
/* Whether or not to work in 'streamable' mode and not
|
||||
* seek to rewrite headers - only valid for fragmented
|
||||
* mode. */
|
||||
* mode. Deprecated */
|
||||
gboolean streamable;
|
||||
|
||||
/* Requested target maximum duration */
|
||||
|
|
|
@ -179,7 +179,7 @@ GstQTMuxFormatProp gst_qt_mux_format_list[] = {
|
|||
GST_RANK_PRIMARY,
|
||||
"qtmux",
|
||||
"QuickTime",
|
||||
"GstQTMux",
|
||||
"GstQTMuxElement",
|
||||
GST_STATIC_CAPS ("video/quicktime, variant = (string) apple; "
|
||||
"video/quicktime"),
|
||||
GST_STATIC_CAPS ("video/x-raw, "
|
||||
|
|
|
@ -444,6 +444,158 @@ check_qtmux_pad_fragmented (GstStaticPadTemplate * srctemplate,
|
|||
buffers = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
check_qtmux_pad_fragmented_finalise (GstStaticPadTemplate * srctemplate,
|
||||
const gchar * sinkname, guint32 dts_method, gboolean streamable)
|
||||
{
|
||||
GstElement *qtmux;
|
||||
GstBuffer *inbuffer, *outbuffer;
|
||||
GstCaps *caps;
|
||||
int num_buffers;
|
||||
int i;
|
||||
guint8 data0[12] = "\000\000\000\024ftypqt ";
|
||||
guint8 data1[4] = "mdat";
|
||||
guint8 data2[4] = "moov";
|
||||
guint8 data3[4] = "moof";
|
||||
GstSegment segment;
|
||||
|
||||
qtmux = setup_qtmux (srctemplate, sinkname, !streamable);
|
||||
g_object_set (qtmux, "dts-method", dts_method, NULL);
|
||||
g_object_set (qtmux, "fragment-duration", 40, NULL);
|
||||
g_object_set (qtmux, "fragment-mode", 1, NULL);
|
||||
g_object_set (qtmux, "streamable", streamable, NULL);
|
||||
fail_unless (gst_element_set_state (qtmux,
|
||||
GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
|
||||
"could not set to playing");
|
||||
|
||||
gst_pad_push_event (mysrcpad, gst_event_new_stream_start ("test"));
|
||||
|
||||
caps = gst_pad_get_pad_template_caps (mysrcpad);
|
||||
gst_pad_set_caps (mysrcpad, caps);
|
||||
gst_caps_unref (caps);
|
||||
|
||||
/* ensure segment (format) properly setup */
|
||||
gst_segment_init (&segment, GST_FORMAT_TIME);
|
||||
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment)));
|
||||
|
||||
inbuffer = gst_buffer_new_and_alloc (1);
|
||||
gst_buffer_memset (inbuffer, 0, 0, 1);
|
||||
GST_BUFFER_TIMESTAMP (inbuffer) = 0 * 40 * GST_MSECOND;
|
||||
GST_BUFFER_DURATION (inbuffer) = 40 * GST_MSECOND;
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
|
||||
fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK);
|
||||
|
||||
inbuffer = gst_buffer_new_and_alloc (1);
|
||||
gst_buffer_memset (inbuffer, 0, 0, 1);
|
||||
GST_BUFFER_TIMESTAMP (inbuffer) = 1 * 40 * GST_MSECOND;
|
||||
GST_BUFFER_DURATION (inbuffer) = 40 * GST_MSECOND;
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
|
||||
fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK);
|
||||
|
||||
inbuffer = gst_buffer_new_and_alloc (1);
|
||||
gst_buffer_memset (inbuffer, 0, 0, 1);
|
||||
GST_BUFFER_TIMESTAMP (inbuffer) = 2 * 40 * GST_MSECOND;
|
||||
GST_BUFFER_DURATION (inbuffer) = 40 * GST_MSECOND;
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
|
||||
fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK);
|
||||
|
||||
/* send eos to have all written */
|
||||
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ()) == TRUE);
|
||||
|
||||
wait_for_eos ();
|
||||
|
||||
num_buffers = g_list_length (buffers);
|
||||
fail_unless (num_buffers >= 13);
|
||||
|
||||
/* clean up first to clear any pending refs in sticky caps */
|
||||
cleanup_qtmux (qtmux, sinkname);
|
||||
|
||||
for (i = 0; i < num_buffers; ++i) {
|
||||
outbuffer = GST_BUFFER (buffers->data);
|
||||
fail_if (outbuffer == NULL);
|
||||
buffers = g_list_remove (buffers, outbuffer);
|
||||
|
||||
switch (i) {
|
||||
case 0:
|
||||
{
|
||||
/* ftyp header */
|
||||
fail_unless (gst_buffer_get_size (outbuffer) >= 20);
|
||||
fail_unless (gst_buffer_memcmp (outbuffer, 0, data0,
|
||||
sizeof (data0)) == 0);
|
||||
fail_unless (gst_buffer_memcmp (outbuffer, 16, data0 + 8, 4) == 0);
|
||||
break;
|
||||
}
|
||||
case 1: /* first moov mdat header */
|
||||
fail_unless_equals_int (gst_buffer_get_size (outbuffer), 16);
|
||||
fail_unless (gst_buffer_memcmp (outbuffer, 12, data1,
|
||||
sizeof (data1)) == 0);
|
||||
break;
|
||||
case 2: /* buffer we put in */
|
||||
fail_unless_equals_int (gst_buffer_get_size (outbuffer), 1);
|
||||
break;
|
||||
case 3: /* first moov mdat header rewrite */
|
||||
fail_unless_equals_int (gst_buffer_get_size (outbuffer), 16);
|
||||
fail_unless (gst_buffer_memcmp (outbuffer, 12, data1,
|
||||
sizeof (data1)) == 0);
|
||||
break;
|
||||
case 4: /* moov */
|
||||
fail_unless (gst_buffer_get_size (outbuffer) > 8);
|
||||
fail_unless (gst_buffer_memcmp (outbuffer, 4, data2,
|
||||
sizeof (data2)) == 0);
|
||||
break;
|
||||
case 5: /* fragment mdat header size == 0 */
|
||||
fail_unless_equals_int (gst_buffer_get_size (outbuffer), 16);
|
||||
fail_unless (gst_buffer_memcmp (outbuffer, 12, data1,
|
||||
sizeof (data1)) == 0);
|
||||
break;
|
||||
case 6: /* buffer we put in */
|
||||
fail_unless_equals_int (gst_buffer_get_size (outbuffer), 1);
|
||||
break;
|
||||
case 7: /* fragment mdat header size */
|
||||
fail_unless_equals_int (gst_buffer_get_size (outbuffer), 16);
|
||||
fail_unless (gst_buffer_memcmp (outbuffer, 12, data1,
|
||||
sizeof (data1)) == 0);
|
||||
break;
|
||||
case 8: /* moof */
|
||||
fail_unless (gst_buffer_get_size (outbuffer) > 8);
|
||||
fail_unless (gst_buffer_memcmp (outbuffer, 4, data3,
|
||||
sizeof (data3)) == 0);
|
||||
break;
|
||||
case 9: /* fragment mdat header size = 0 */
|
||||
fail_unless_equals_int (gst_buffer_get_size (outbuffer), 16);
|
||||
fail_unless (gst_buffer_memcmp (outbuffer, 12, data1,
|
||||
sizeof (data1)) == 0);
|
||||
break;
|
||||
case 10: /* buffer we put in */
|
||||
fail_unless_equals_int (gst_buffer_get_size (outbuffer), 1);
|
||||
break;
|
||||
case 11: /* initial moov->hoov */
|
||||
fail_unless_equals_int (gst_buffer_get_size (outbuffer), 1);
|
||||
fail_unless (gst_buffer_memcmp (outbuffer, 0, "h", 1) == 0);
|
||||
break;
|
||||
case 12: /* final moov mdat header size */
|
||||
fail_unless_equals_int (gst_buffer_get_size (outbuffer), 16);
|
||||
fail_unless (gst_buffer_memcmp (outbuffer, 12, data1,
|
||||
sizeof (data1)) == 0);
|
||||
break;
|
||||
case 13: /* final moov */
|
||||
fail_unless (gst_buffer_get_size (outbuffer) > 8);
|
||||
fail_unless (gst_buffer_memcmp (outbuffer, 4, data2,
|
||||
sizeof (data2)) == 0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1);
|
||||
gst_buffer_unref (outbuffer);
|
||||
outbuffer = NULL;
|
||||
}
|
||||
|
||||
g_list_free (buffers);
|
||||
buffers = NULL;
|
||||
}
|
||||
|
||||
/* dts-method dd */
|
||||
|
||||
GST_START_TEST (test_video_pad_dd)
|
||||
|
@ -475,7 +627,6 @@ GST_START_TEST (test_audio_pad_frag_dd)
|
|||
|
||||
GST_END_TEST;
|
||||
|
||||
|
||||
GST_START_TEST (test_video_pad_frag_dd_streamable)
|
||||
{
|
||||
check_qtmux_pad_fragmented (&srcvideotemplate, "video_%u", 0, TRUE);
|
||||
|
@ -483,7 +634,6 @@ GST_START_TEST (test_video_pad_frag_dd_streamable)
|
|||
|
||||
GST_END_TEST;
|
||||
|
||||
|
||||
GST_START_TEST (test_audio_pad_frag_dd_streamable)
|
||||
{
|
||||
check_qtmux_pad_fragmented (&srcaudiotemplate, "audio_%u", 0, TRUE);
|
||||
|
@ -491,6 +641,13 @@ GST_START_TEST (test_audio_pad_frag_dd_streamable)
|
|||
|
||||
GST_END_TEST;
|
||||
|
||||
GST_START_TEST (test_video_pad_frag_dd_finalise)
|
||||
{
|
||||
check_qtmux_pad_fragmented_finalise (&srcvideotemplate, "video_%u", 0, FALSE);
|
||||
}
|
||||
|
||||
GST_END_TEST;
|
||||
|
||||
/* dts-method reorder */
|
||||
|
||||
GST_START_TEST (test_video_pad_reorder)
|
||||
|
@ -538,6 +695,13 @@ GST_START_TEST (test_audio_pad_frag_reorder_streamable)
|
|||
|
||||
GST_END_TEST;
|
||||
|
||||
GST_START_TEST (test_video_pad_frag_reorder_finalise)
|
||||
{
|
||||
check_qtmux_pad_fragmented_finalise (&srcvideotemplate, "video_%u", 1, FALSE);
|
||||
}
|
||||
|
||||
GST_END_TEST;
|
||||
|
||||
/* dts-method asc */
|
||||
|
||||
GST_START_TEST (test_video_pad_asc)
|
||||
|
@ -585,6 +749,13 @@ GST_START_TEST (test_audio_pad_frag_asc_streamable)
|
|||
|
||||
GST_END_TEST;
|
||||
|
||||
GST_START_TEST (test_video_pad_frag_asc_finalise)
|
||||
{
|
||||
check_qtmux_pad_fragmented_finalise (&srcvideotemplate, "video_%u", 2, FALSE);
|
||||
}
|
||||
|
||||
GST_END_TEST;
|
||||
|
||||
GST_START_TEST (test_reuse)
|
||||
{
|
||||
GstElement *qtmux = setup_qtmux (&srcvideotemplate, "video_%u", TRUE);
|
||||
|
@ -1651,6 +1822,7 @@ qtmux_suite (void)
|
|||
tcase_add_test (tc_chain, test_audio_pad_frag_dd);
|
||||
tcase_add_test (tc_chain, test_video_pad_frag_dd_streamable);
|
||||
tcase_add_test (tc_chain, test_audio_pad_frag_dd_streamable);
|
||||
tcase_add_test (tc_chain, test_video_pad_frag_dd_finalise);
|
||||
|
||||
tcase_add_test (tc_chain, test_video_pad_reorder);
|
||||
tcase_add_test (tc_chain, test_audio_pad_reorder);
|
||||
|
@ -1658,6 +1830,7 @@ qtmux_suite (void)
|
|||
tcase_add_test (tc_chain, test_audio_pad_frag_reorder);
|
||||
tcase_add_test (tc_chain, test_video_pad_frag_reorder_streamable);
|
||||
tcase_add_test (tc_chain, test_audio_pad_frag_reorder_streamable);
|
||||
tcase_add_test (tc_chain, test_video_pad_frag_reorder_finalise);
|
||||
|
||||
tcase_add_test (tc_chain, test_video_pad_asc);
|
||||
tcase_add_test (tc_chain, test_audio_pad_asc);
|
||||
|
@ -1665,6 +1838,7 @@ qtmux_suite (void)
|
|||
tcase_add_test (tc_chain, test_audio_pad_frag_asc);
|
||||
tcase_add_test (tc_chain, test_video_pad_frag_asc_streamable);
|
||||
tcase_add_test (tc_chain, test_audio_pad_frag_asc_streamable);
|
||||
tcase_add_test (tc_chain, test_video_pad_frag_asc_finalise);
|
||||
|
||||
tcase_add_test (tc_chain, test_average_bitrate);
|
||||
|
||||
|
|
Loading…
Reference in a new issue