guint64 * offset)
{
guint64 original_offset = *offset;
- guint i;
+ guint i, len;
if (!atom_full_copy_data (&stsc->header, buffer, size, offset)) {
return 0;
}
+ /* Last two entries might be the same size here as we only merge once the
+ * next chunk is started */
+ if ((len = atom_array_get_len (&stsc->entries)) > 1 &&
+ ((atom_array_index (&stsc->entries, len - 1)).samples_per_chunk ==
+ (atom_array_index (&stsc->entries, len - 2)).samples_per_chunk)) {
+ stsc->entries.len--;
+ }
+
prop_copy_uint32 (atom_array_get_len (&stsc->entries), buffer, size, offset);
/* minimize realloc */
prop_copy_ensure_buffer (buffer, size, offset,
static void
atom_stsc_add_new_entry (AtomSTSC * stsc, guint32 first_chunk, guint32 nsamples)
{
- STSCEntry nentry;
gint len;
if ((len = atom_array_get_len (&stsc->entries)) &&
nsamples))
return;
- nentry.first_chunk = first_chunk;
- nentry.samples_per_chunk = nsamples;
- nentry.sample_description_index = 1;
- atom_array_append (&stsc->entries, nentry, 128);
+ if ((len = atom_array_get_len (&stsc->entries)) > 1 &&
+ ((atom_array_index (&stsc->entries, len - 1)).samples_per_chunk ==
+ (atom_array_index (&stsc->entries, len - 2)).samples_per_chunk)) {
+ STSCEntry *nentry;
+
+ /* Merge last two entries as they have the same number of samples per chunk */
+ nentry = &atom_array_index (&stsc->entries, len - 1);
+ nentry->first_chunk = first_chunk;
+ nentry->samples_per_chunk = nsamples;
+ nentry->sample_description_index = 1;
+ } else {
+ STSCEntry nentry;
+
+ nentry.first_chunk = first_chunk;
+ nentry.samples_per_chunk = nsamples;
+ nentry.sample_description_index = 1;
+ atom_array_append (&stsc->entries, nentry, 128);
+ }
+}
+
+static void
+atom_stsc_update_entry (AtomSTSC * stsc, guint32 first_chunk, guint32 nsamples)
+{
+ gint len;
+
+ len = atom_array_get_len (&stsc->entries);
+ g_assert (len != 0);
+ g_assert (atom_array_index (&stsc->entries,
+ len - 1).first_chunk == first_chunk);
+
+ atom_array_index (&stsc->entries, len - 1).samples_per_chunk += nsamples;
}
static void
return atom_array_get_len (&stco64->entries);
}
-static void
+/* returns TRUE if a new entry was added */
+static gboolean
atom_stco64_add_entry (AtomSTCO64 * stco64, guint64 entry)
{
+ guint32 len;
+
+ /* Only add a new entry if the chunk offset changed */
+ if ((len = atom_array_get_len (&stco64->entries)) &&
+ ((atom_array_index (&stco64->entries, len - 1)) == entry))
+ return FALSE;
+
atom_array_append (&stco64->entries, entry, 256);
if (entry > G_MAXUINT32)
stco64->header.header.type = FOURCC_co64;
+
+ return TRUE;
}
void
{
atom_stts_add_entry (&stbl->stts, nsamples, delta);
atom_stsz_add_entry (&stbl->stsz, nsamples, size);
- atom_stco64_add_entry (&stbl->stco64, chunk_offset);
- atom_stsc_add_new_entry (&stbl->stsc,
- atom_stco64_get_entry_count (&stbl->stco64), nsamples);
+ if (atom_stco64_add_entry (&stbl->stco64, chunk_offset)) {
+ atom_stsc_add_new_entry (&stbl->stsc,
+ atom_stco64_get_entry_count (&stbl->stco64), nsamples);
+ } else {
+ atom_stsc_update_entry (&stbl->stsc,
+ atom_stco64_get_entry_count (&stbl->stco64), nsamples);
+ }
+
if (sync)
atom_stbl_add_stss_entry (stbl);
/* always store to arrange for consistent content */
PROP_DTS_METHOD,
#endif
PROP_DO_CTTS,
+ PROP_INTERLEAVE_BYTES,
+ PROP_INTERLEAVE_TIME,
};
/* some spare for header size as well */
#define DEFAULT_RESERVED_MAX_DURATION GST_CLOCK_TIME_NONE
#define DEFAULT_RESERVED_MOOV_UPDATE_PERIOD GST_CLOCK_TIME_NONE
#define DEFAULT_RESERVED_BYTES_PER_SEC_PER_TRAK 550
+#define DEFAULT_INTERLEAVE_BYTES 0
+#define DEFAULT_INTERLEAVE_TIME 0
static void gst_qt_mux_finalize (GObject * object);
static gboolean gst_qt_mux_sink_event (GstCollectPads * pads,
GstCollectData * data, GstEvent * event, gpointer user_data);
-static GstFlowReturn gst_qt_mux_handle_buffer (GstCollectPads * pads,
- GstCollectData * cdata, GstBuffer * buf, gpointer user_data);
+static GstFlowReturn gst_qt_mux_collected (GstCollectPads * pads,
+ gpointer user_data);
static GstFlowReturn gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad,
GstBuffer * buf);
"Multiplier for converting reserved-max-duration into bytes of header to reserve, per second, per track",
0, 10000, DEFAULT_RESERVED_BYTES_PER_SEC_PER_TRAK,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_INTERLEAVE_BYTES,
+ g_param_spec_uint64 ("interleave-bytes", "Interleave (bytes)",
+ "Interleave between streams in bytes",
+ 0, G_MAXUINT64, DEFAULT_INTERLEAVE_BYTES,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_INTERLEAVE_TIME,
+ g_param_spec_uint64 ("interleave-time", "Interleave (time)",
+ "Interleave between streams in nanoseconds",
+ 0, G_MAXUINT64, DEFAULT_INTERLEAVE_TIME,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
gstelement_class->request_new_pad =
GST_DEBUG_FUNCPTR (gst_qt_mux_request_new_pad);
}
}
+ qtmux->current_pad = NULL;
+ qtmux->current_chunk_size = 0;
+ qtmux->current_chunk_duration = 0;
+ qtmux->current_chunk_offset = -1;
+
qtmux->reserved_moov_size = 0;
qtmux->last_moov_update = GST_CLOCK_TIME_NONE;
qtmux->muxed_since_last_update = 0;
qtmux->sinkpads = NULL;
qtmux->collect = gst_collect_pads_new ();
- gst_collect_pads_set_buffer_function (qtmux->collect,
- GST_DEBUG_FUNCPTR (gst_qt_mux_handle_buffer), qtmux);
gst_collect_pads_set_event_function (qtmux->collect,
GST_DEBUG_FUNCPTR (gst_qt_mux_sink_event), qtmux);
gst_collect_pads_set_clip_function (qtmux->collect,
GST_DEBUG_FUNCPTR (gst_collect_pads_clip_running_time), qtmux);
+ gst_collect_pads_set_function (qtmux->collect,
+ GST_DEBUG_FUNCPTR (gst_qt_mux_collected), qtmux);
/* properties set to default upon construction */
qtmux->reserved_moov_update_period = DEFAULT_RESERVED_MOOV_UPDATE_PERIOD;
qtmux->reserved_bytes_per_sec_per_trak =
DEFAULT_RESERVED_BYTES_PER_SEC_PER_TRAK;
+ qtmux->interleave_bytes = DEFAULT_INTERLEAVE_BYTES;
+ qtmux->interleave_time = DEFAULT_INTERLEAVE_TIME;
/* always need this */
qtmux->context =
pad->total_duration += duration;
}
+ if (qtmux->current_pad != pad || qtmux->current_chunk_offset == -1) {
+ GST_DEBUG_OBJECT (qtmux,
+ "Switching to next chunk for pad %s:%s: offset %" G_GUINT64_FORMAT
+ ", size %" G_GUINT64_FORMAT ", duration %" GST_TIME_FORMAT,
+ GST_DEBUG_PAD_NAME (pad->collect.pad), qtmux->current_chunk_offset,
+ qtmux->current_chunk_size,
+ GST_TIME_ARGS (qtmux->current_chunk_duration));
+ qtmux->current_pad = pad;
+ if (qtmux->current_chunk_offset == -1)
+ qtmux->current_chunk_offset = qtmux->mdat_size;
+ else
+ qtmux->current_chunk_offset += qtmux->current_chunk_size;
+ qtmux->current_chunk_size = 0;
+ qtmux->current_chunk_duration = 0;
+ }
+ qtmux->current_chunk_size += gst_buffer_get_size (last_buf);
+ qtmux->current_chunk_duration += duration;
+
last_dts = gst_util_uint64_scale_round (pad->last_dts,
atom_trak_get_timescale (pad->trak), GST_SECOND);
pad->last_dts += duration;
}
}
- chunk_offset = qtmux->mdat_size;
+
+ chunk_offset = qtmux->current_chunk_offset;
GST_LOG_OBJECT (qtmux,
"Pad (%s) dts updated to %" GST_TIME_FORMAT,
GST_TIME_ARGS (GST_BUFFER_PTS (last_buf)),
(int) (last_dts), (int) (pts_offset));
- /*
- * Each buffer starts a new chunk, so we can assume the buffer
- * duration is the chunk duration
- */
- if (GST_CLOCK_TIME_IS_VALID (duration) && (duration > qtmux->longest_chunk ||
- !GST_CLOCK_TIME_IS_VALID (qtmux->longest_chunk))) {
- GST_DEBUG_OBJECT (qtmux, "New longest chunk found: %" GST_TIME_FORMAT
- ", pad %s", GST_TIME_ARGS (duration), GST_PAD_NAME (pad->collect.pad));
- qtmux->longest_chunk = duration;
+ if (GST_CLOCK_TIME_IS_VALID (duration)
+ && (qtmux->current_chunk_duration > qtmux->longest_chunk
+ || !GST_CLOCK_TIME_IS_VALID (qtmux->longest_chunk))) {
+ GST_DEBUG_OBJECT (qtmux,
+ "New longest chunk found: %" GST_TIME_FORMAT ", pad %s",
+ GST_TIME_ARGS (qtmux->current_chunk_duration),
+ GST_PAD_NAME (pad->collect.pad));
+ qtmux->longest_chunk = qtmux->current_chunk_duration;
}
/* now we go and register this buffer/sample all over */
ret =
gst_qt_mux_register_and_push_sample (qtmux, pad, empty_buf, FALSE, 1,
last_dts + scaled_duration, empty_duration_scaled,
- gst_buffer_get_size (empty_buf), qtmux->mdat_size, sync, TRUE, 0);
+ gst_buffer_get_size (empty_buf), chunk_offset, sync, TRUE, 0);
} else {
/* our only case currently is tx3g subtitles, so there is no reason to fill this yet */
g_assert_not_reached ();
}
}
+static GstQTPad *
+find_best_pad (GstQTMux * qtmux, GstCollectPads * pads)
+{
+ GSList *walk;
+ GstQTPad *best_pad = NULL;
+
+ if (qtmux->current_pad &&
+ (qtmux->interleave_bytes != 0 || qtmux->interleave_time != 0) &&
+ (qtmux->interleave_bytes == 0
+ || 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) {
+ GstBuffer *tmp_buf =
+ gst_collect_pads_peek (pads, (GstCollectData *) qtmux->current_pad);
+
+ if (tmp_buf || qtmux->current_pad->last_buf) {
+ best_pad = qtmux->current_pad;
+ if (tmp_buf)
+ gst_buffer_unref (tmp_buf);
+ GST_DEBUG_OBJECT (qtmux, "Reusing pad %s:%s",
+ GST_DEBUG_PAD_NAME (best_pad->collect.pad));
+ }
+ } else {
+ if (qtmux->current_pad)
+ GST_DEBUG_OBJECT (qtmux, "Switching from pad %s:%s",
+ GST_DEBUG_PAD_NAME (qtmux->current_pad->collect.pad));
+ best_pad = qtmux->current_pad = NULL;
+ }
+
+ if (!best_pad) {
+ GstClockTime best_time = GST_CLOCK_TIME_NONE;
+
+ for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
+ GstCollectData *cdata = (GstCollectData *) walk->data;
+ GstQTPad *qtpad = (GstQTPad *) cdata;
+ GstBuffer *tmp_buf;
+ GstClockTime timestamp;
+
+ tmp_buf = gst_collect_pads_peek (pads, cdata);
+ if (!tmp_buf) {
+ /* This one is newly EOS now, finish it for real */
+ if (qtpad->last_buf) {
+ timestamp = GST_BUFFER_DTS_OR_PTS (qtpad->last_buf);
+ } else {
+ continue;
+ }
+ } else {
+ timestamp = GST_BUFFER_DTS_OR_PTS (tmp_buf);
+ }
+
+ if (best_pad == NULL ||
+ !GST_CLOCK_TIME_IS_VALID (best_time) || timestamp < best_time) {
+ best_pad = qtpad;
+ best_time = timestamp;
+ }
+
+ if (tmp_buf)
+ gst_buffer_unref (tmp_buf);
+ }
+
+ GST_DEBUG_OBJECT (qtmux, "Choosing pad %s:%s",
+ GST_DEBUG_PAD_NAME (best_pad->collect.pad));
+ }
+
+ return best_pad;
+}
+
static GstFlowReturn
-gst_qt_mux_handle_buffer (GstCollectPads * pads, GstCollectData * cdata,
- GstBuffer * buf, gpointer user_data)
+gst_qt_mux_collected (GstCollectPads * pads, gpointer user_data)
{
GstFlowReturn ret = GST_FLOW_OK;
GstQTMux *qtmux = GST_QT_MUX_CAST (user_data);
if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_EOS))
return GST_FLOW_EOS;
- best_pad = (GstQTPad *) cdata;
+ best_pad = find_best_pad (qtmux, pads);
/* clipping already converted to running time */
if (best_pad != NULL) {
- g_assert (buf);
- gst_qt_pad_adjust_buffer_dts (qtmux, best_pad, cdata, &buf);
+ GstBuffer *buf = gst_collect_pads_pop (pads, (GstCollectData *) best_pad);
+
+ g_assert (buf || best_pad->last_buf);
+ if (buf)
+ gst_qt_pad_adjust_buffer_dts (qtmux, best_pad,
+ (GstCollectData *) best_pad, &buf);
+
ret = gst_qt_mux_add_buffer (qtmux, best_pad, buf);
} else {
qtmux->state = GST_QT_MUX_STATE_EOS;
}
}
+ if (mux->current_pad && mux->current_pad->collect.pad == pad) {
+ mux->current_pad = NULL;
+ mux->current_chunk_size = 0;
+ mux->current_chunk_duration = 0;
+ }
+
gst_collect_pads_remove_pad (mux->collect, pad);
}
case PROP_RESERVED_BYTES_PER_SEC:
g_value_set_uint (value, qtmux->reserved_bytes_per_sec_per_trak);
break;
+ case PROP_INTERLEAVE_BYTES:
+ g_value_set_uint64 (value, qtmux->interleave_bytes);
+ break;
+ case PROP_INTERLEAVE_TIME:
+ g_value_set_uint64 (value, qtmux->interleave_time);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
case PROP_RESERVED_BYTES_PER_SEC:
qtmux->reserved_bytes_per_sec_per_trak = g_value_get_uint (value);
break;
+ case PROP_INTERLEAVE_BYTES:
+ qtmux->interleave_bytes = g_value_get_uint64 (value);
+ qtmux->interleave_bytes_set = TRUE;
+ break;
+ case PROP_INTERLEAVE_TIME:
+ qtmux->interleave_time = g_value_get_uint64 (value);
+ qtmux->interleave_time_set = TRUE;
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;