isomp4: Implement robust muxing using ping-pong strategy

Implement a robust recording mode, where the output
file is always in a playable state, seeking and rewriting
the moov header at a configurable interval. Rewriting
moov is done using reserved space at the start of
the file, and a ping-pong strategy where the moov
is replaced atomically so it's never invalid.

Track when tags have actually changed, and don't write them into
the moov unless they've changed. Clear any existing tags when
re-writing them, so we can do progressive moov updating in robust
recording mode.

Write placeholder mdat as a free atom plus a 32-bit mdat
with '0' size, which means "rest of the file" in the spec.

Re-write it later to a full 64-bit extended size atom if needed.
This commit is contained in:
Jan Schmidt 2015-04-01 11:15:38 +11:00
parent 3d7b343525
commit 1d058c7d8a
5 changed files with 782 additions and 202 deletions

View file

@ -1132,6 +1132,20 @@ atom_udta_clear (AtomUDTA * udta)
atom_info_list_free (udta->entries);
}
/* Clear added tags, but keep the context/flavor the same */
void
atom_udta_clear_tags (AtomUDTA * udta)
{
if (udta->entries) {
atom_info_list_free (udta->entries);
udta->entries = NULL;
}
if (udta->meta && udta->meta->ilst->entries) {
atom_info_list_free (udta->meta->ilst->entries);
udta->meta->ilst->entries = NULL;
}
}
static void
atom_tag_data_init (AtomTagData * data)
{
@ -1980,12 +1994,13 @@ atom_stco64_copy_data (AtomSTCO64 * stco64, guint8 ** buffer, guint64 * size,
prop_copy_ensure_buffer (buffer, size, offset,
8 * atom_array_get_len (&stco64->entries));
for (i = 0; i < atom_array_get_len (&stco64->entries); i++) {
guint64 *value = &atom_array_index (&stco64->entries, i);
guint64 value =
atom_array_index (&stco64->entries, i) + stco64->chunk_offset;
if (trunc_to_32) {
prop_copy_uint32 ((guint32) * value, buffer, size, offset);
prop_copy_uint32 ((guint32) value, buffer, size, offset);
} else {
prop_copy_uint64 (*value, buffer, size, offset);
prop_copy_uint64 (value, buffer, size, offset);
}
}
@ -2749,6 +2764,12 @@ atom_moov_add_trak (AtomMOOV * moov, AtomTRAK * trak)
atom_moov_add_trex (moov, atom_trex_new (trak));
}
guint
atom_moov_get_trak_count (AtomMOOV * moov)
{
return g_list_length (moov->traks);
}
static guint64
atom_trak_get_duration (AtomTRAK * trak)
{
@ -2821,28 +2842,27 @@ atom_moov_set_fragmented (AtomMOOV * moov, gboolean fragmented)
}
void
atom_stco64_chunks_add_offset (AtomSTCO64 * stco64, guint32 offset)
atom_stco64_chunks_set_offset (AtomSTCO64 * stco64, guint32 offset)
{
guint i;
for (i = 0; i < atom_array_get_len (&stco64->entries); i++) {
guint64 *value = &atom_array_index (&stco64->entries, i);
*value += offset;
}
stco64->chunk_offset = offset;
}
void
atom_moov_chunks_add_offset (AtomMOOV * moov, guint32 offset)
atom_moov_chunks_set_offset (AtomMOOV * moov, guint32 offset)
{
GList *traks = moov->traks;
if (offset == moov->chunks_offset)
return; /* Nothing to do */
while (traks) {
AtomTRAK *trak = (AtomTRAK *) traks->data;
atom_stco64_chunks_add_offset (&trak->mdia.minf.stbl.stco64, offset);
atom_stco64_chunks_set_offset (&trak->mdia.minf.stbl.stco64, offset);
traks = g_list_next (traks);
}
moov->chunks_offset = offset;
}
void

View file

@ -478,7 +478,8 @@ typedef struct _AtomSTSC
typedef struct _AtomSTCO64
{
AtomFull header;
/* Global offset to add to entries when serialising */
guint32 chunk_offset;
ATOM_ARRAY (guint64) entries;
} AtomSTCO64;
@ -754,6 +755,7 @@ typedef struct _AtomMOOV
AtomUDTA udta;
gboolean fragmented;
guint32 chunks_offset;
} AtomMOOV;
typedef struct _AtomWAVE
@ -843,12 +845,13 @@ guint64 atom_moov_copy_data (AtomMOOV *atom, guint8 **buffer, guint64
void atom_moov_update_timescale (AtomMOOV *moov, guint32 timescale);
void atom_moov_update_duration (AtomMOOV *moov);
void atom_moov_set_fragmented (AtomMOOV *moov, gboolean fragmented);
void atom_moov_chunks_add_offset (AtomMOOV *moov, guint32 offset);
void atom_moov_chunks_set_offset (AtomMOOV *moov, guint32 offset);
void atom_moov_add_trak (AtomMOOV *moov, AtomTRAK *trak);
guint atom_moov_get_trak_count (AtomMOOV *moov);
guint64 atom_mvhd_copy_data (AtomMVHD * atom, guint8 ** buffer,
guint64 * size, guint64 * offset);
void atom_stco64_chunks_add_offset (AtomSTCO64 * stco64, guint32 offset);
void atom_stco64_chunks_set_offset (AtomSTCO64 * stco64, guint32 offset);
guint64 atom_trak_copy_data (AtomTRAK * atom, guint8 ** buffer,
guint64 * size, guint64 * offset);
void atom_stbl_clear (AtomSTBL * stbl);
@ -974,6 +977,7 @@ AtomInfo * build_uuid_xmp_atom (GstBuffer * xmp);
/*
* Meta tags functions
*/
void atom_udta_clear_tags (AtomUDTA *udta);
void atom_udta_add_str_tag (AtomUDTA *udta, guint32 fourcc, const gchar *value);
void atom_udta_add_uint_tag (AtomUDTA *udta, guint32 fourcc, guint32 flags,
guint32 value);

View file

@ -972,7 +972,7 @@ moov_recov_write_file (MoovRecovFile * moovrf, MdatRecovFile * mdatrf,
TrakRecovData *trak = &(moovrf->traks_rd[i]);
/* 16 for the mdat header */
gint64 offset = moov_size + ftell (outf) + 16;
atom_stco64_chunks_add_offset (&trak->stbl.stco64, offset);
atom_stco64_chunks_set_offset (&trak->stbl.stco64, offset);
}
/* write the moov */

File diff suppressed because it is too large Load diff

View file

@ -128,6 +128,9 @@ struct _GstQTPad
/* optional fragment index book-keeping */
AtomTFRA *tfra;
/* Set when tags are received, cleared when written to moov */
gboolean tags_changed;
GstTagList *tags;
/* if nothing is set, it won't be called */
@ -148,7 +151,8 @@ 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_FAST_START,
GST_QT_MUX_MODE_ROBUST_RECORDING
} GstQtMuxMode;
struct _GstQTMux
@ -168,14 +172,23 @@ struct _GstQTMux
/* size of header (prefix, atoms (ftyp, possibly moov, mdat header)) */
guint64 header_size;
/* accumulated size of raw media data (a priori not including mdat header) */
/* accumulated size of raw media data (not including mdat header) */
guint64 mdat_size;
/* position of mdat atom (for later updating) */
/* position of the moov (for fragmented mode) or reserved moov atom
* area (for robust-muxing mode) */
guint64 moov_pos;
/* position of mdat atom header (for later updating of size) in
* moov-at-end, fragmented and robust-muxing modes */
guint64 mdat_pos;
/* keep track of the largest chunk to fine-tune brands */
GstClockTime longest_chunk;
/* Earliest timestamp across all pads/traks */
GstClockTime first_ts;
/* Last DTS across all pads (= duration) */
GstClockTime last_dts;
/* atom helper objects */
AtomsContext *context;
AtomFTYP *ftyp;
@ -183,6 +196,10 @@ struct _GstQTMux
GSList *extra_atoms; /* list of extra top-level atoms (e.g. UUID for xmp)
* Stored as AtomInfo structs */
/* Set when tags are received, cleared when written to moov */
gboolean tags_changed;
/* fragmented file index */
AtomMFRA *mfra;
@ -212,6 +229,30 @@ struct _GstQTMux
* mode. */
gboolean streamable;
/* Requested target maximum duration */
GstClockTime reserved_max_duration;
/* Estimate of remaining reserved header space (in ns of recording) */
GstClockTime reserved_duration_remaining;
/* Multiplier for conversion from reserved_max_duration to bytes */
guint reserved_bytes_per_sec_per_trak;
/* Reserved minimum MOOV size in bytes
* This is converted from reserved_max_duration
* using the bytes/trak/sec estimate */
guint32 reserved_moov_size;
/* Basic size of the moov (static headers + tags) */
guint32 base_moov_size;
/* Size of the most recently generated moov header */
guint32 last_moov_size;
/* True if the first moov in the ping-pong buffers
* is the active one. See gst_qt_mux_robust_recording_rewrite_moov() */
gboolean reserved_moov_first_active;
/* Tracking of periodic MOOV updates */
GstClockTime last_moov_update;
GstClockTime reserved_moov_update_period;
GstClockTime muxed_since_last_update;
/* for request pad naming */
guint video_pads, audio_pads, subtitle_pads;
};