PROP_INTERLEAVE_BYTES,
PROP_INTERLEAVE_TIME,
PROP_MAX_RAW_AUDIO_DRIFT,
+ PROP_START_GAP_THRESHOLD,
+#ifdef TIZEN_FEATURE_GST_MUX_ENHANCEMENT
+ PROP_EXPECTED_TRAILER_SIZE,
+#endif /* TIZEN_FEATURE_GST_MUX_ENHANCEMENT */
};
/* some spare for header size as well */
#define DEFAULT_INTERLEAVE_BYTES 0
#define DEFAULT_INTERLEAVE_TIME 250*GST_MSECOND
#define DEFAULT_MAX_RAW_AUDIO_DRIFT 40 * GST_MSECOND
+#define DEFAULT_START_GAP_THRESHOLD 0
static void gst_qt_mux_finalize (GObject * object);
static GstElementClass *parent_class = NULL;
+#ifdef TIZEN_FEATURE_GST_MUX_ENHANCEMENT
+/*
+ [[ Metadata Size ]]
+ 1. Common
+ free = 8
+ moov = 8
+ mvhd = 108
+ -------------
+ total : 124
+
+ 2. Video
+ i. Video common
+ trak = 8
+ tkhd = 92
+ mdia = 8
+ mdhd = 32
+ hdlr = 45
+ minf = 8
+ vmhd = 20
+ dinf = 36 (8, dref : 16 , url : 12)
+ stbl = 8
+ ---------------
+ total : 257
+
+ ii. Variation in file format
+ - MP4
+ ftyp = 32
+ udta = 61
+ - 3GP
+ ftyp = 28
+ udta = 8
+
+ iii. Variation in codec
+ - MPEG4
+ stsd = 137(16, mp4v : 86, esds : 35)
+
+ - H.264 = 487(or 489) + (8*stts_count) + (8*frame) + (4*I-frame)
+ stsd = 134 (SPS 9, PPS 4) or 136 (SPS 111, PPS 4)
+
+ - H.263 = 470 + + (8*stts_count) + (8*frame) + (4*I-frame)
+ stsd = 102 -> different from H.264
+
+ iv. Variation in frame
+ stts = 16 + (8*stts_count)
+ stss = 16 + (4*I-frame)
+ stsc = 28
+ stsz = 20 + (4*frame)
+ stco = 16 + (4*frame)
+
+ 3. Audio
+ i. Audio common
+ trak = 8
+ tkhd = 92
+ mdia = 8
+ mdhd = 32
+ hdlr = 45
+ minf = 8
+ smhd = 16
+ dinf = 36 (8, dref : 16, url : 12)
+ stbl = 8
+ ---------------
+ total : 253
+
+ stts = 16
+ stsz = 20
+ stco = 16
+ ------------
+ total : 52
+
+ ii. Variation in file format
+ - MP4
+ udta = 61
+ - 3GP
+ udta = 8
+
+ iii. Variation in codec
+ - Common
+ stts = 16 + (8*stts_count)
+ stsc = 28
+ stsz = 20 + (4*frame)
+ stco = 16 + (4*frame)
+
+ - AAC
+ stsd = 94 (16, mp4a : 78(36 ,esds : 42))
+
+ - AMR
+ stsd = 69 (16, samr : 53(36, damr : 17))
+*/
+
+/* trailer entry size */
+#define ENTRY_SIZE_VIDEO_STTS 8
+#define ENTRY_SIZE_VIDEO_STSS 4
+#define ENTRY_SIZE_VIDEO_STSZ 4
+#define ENTRY_SIZE_VIDEO_STCO 4
+#define ENTRY_SIZE_AUDIO_STTS 8
+#define ENTRY_SIZE_AUDIO_STSZ 4
+#define ENTRY_SIZE_AUDIO_STCO 4
+
+#define ENTRY_SIZE_VIDEO_MPEG4_STSD 137
+#define ENTRY_SIZE_VIDEO_H263P_STSD 102
+#define ENTRY_SIZE_AUDIO_AAC_STSD 94
+#define ENTRY_SIZE_AUDIO_AMR_STSD 69
+
+#define ENTRY_SIZE_STSC 28
+#define ENTRY_SIZE_VIDEO_ST 68 /*atom size (stss + stts + stsc + stsz + stco ) * (size + atom + version + flags + sample count)+stsz(sample size) */
+#define ENTRY_SIZE_AUDIO_ST 52 /*atom size (stss + stsc + stsz + stco ) * (size + atom + version + flags + sample count)+stsz(sample size) */
+
+/* common */
+#define MUX_COMMON_SIZE_HEADER 124 /* free + moov + moov.mvhd*/
+
+#define MUX_COMMON_SIZE_VIDEO_HEADER 257
+#define MUX_COMMON_SIZE_AUDIO_HEADER 253
+
+#define MUX_COMMON_SIZE_MP4_FTYP 32
+#define MUX_COMMON_SIZE_3GP_FTYP 28
+
+#define MUX_COMMON_SIZE_MP4_UDTA 61
+#define MUX_COMMON_SIZE_3GP_UDTA 8
+
+static void
+gst_qt_mux_update_expected_trailer_size (GstQTMux *qtmux, GstQTPad *pad)
+{
+ guint nb_video_frames = 0;
+ guint nb_video_i_frames = 0;
+ guint nb_video_stts_entry = 0;
+ guint nb_audio_frames = 0;
+ guint nb_audio_stts_entry = 0;
+ gboolean video_stream = FALSE;
+ gboolean audio_stream = FALSE;
+ guint exp_size = 0;
+ GstQTMuxClass *qtmux_klass = NULL;
+
+ if (qtmux == NULL || pad == NULL) {
+ GST_ERROR_OBJECT (qtmux, "Invalid parameter");
+ return;
+ }
+
+ qtmux_klass = (GstQTMuxClass *)(G_OBJECT_GET_CLASS(qtmux));
+
+ if (!strncmp(GST_PAD_NAME(pad->collect.pad), "video", 5)) {
+ nb_video_frames += pad->trak->mdia.minf.stbl.stsz.table_size;
+ nb_video_i_frames += pad->trak->mdia.minf.stbl.stss.entries.len;
+ nb_video_stts_entry += pad->trak->mdia.minf.stbl.stts.entries.len;
+
+ video_stream = TRUE;
+ } else if (!strncmp(GST_PAD_NAME(pad->collect.pad), "audio", 5)) {
+ nb_audio_frames += pad->trak->mdia.minf.stbl.stsz.table_size;
+ nb_audio_stts_entry += pad->trak->mdia.minf.stbl.stts.entries.len;
+
+ audio_stream = TRUE;
+ }
+
+ /* free + moov + mvhd */
+ qtmux->expected_trailer_size = MUX_COMMON_SIZE_HEADER;
+
+ /* ftyp + udta * 3 (There is 3 udta fields and it's same size) */
+ switch (qtmux_klass->format) {
+ case GST_QT_MUX_FORMAT_MP4:
+ qtmux->expected_trailer_size += MUX_COMMON_SIZE_MP4_FTYP + MUX_COMMON_SIZE_MP4_UDTA * 3;
+ break;
+ case GST_QT_MUX_FORMAT_3GP:
+ qtmux->expected_trailer_size += MUX_COMMON_SIZE_3GP_FTYP + MUX_COMMON_SIZE_3GP_UDTA * 3;
+ break;
+ default:
+ break;
+ }
+
+ /* Calculate trailer size for video stream */
+ if (video_stream) {
+ switch (pad->fourcc) {
+ case FOURCC_h263:
+ case FOURCC_s263:
+ exp_size += MUX_COMMON_SIZE_VIDEO_HEADER + ENTRY_SIZE_VIDEO_H263P_STSD;
+ break;
+ case FOURCC_mp4v:
+ case FOURCC_MP4V:
+ case FOURCC_fmp4:
+ case FOURCC_FMP4:
+ case FOURCC_3gp4:
+ case FOURCC_3gp6:
+ case FOURCC_3gg6:
+ exp_size += MUX_COMMON_SIZE_VIDEO_HEADER + ENTRY_SIZE_VIDEO_MPEG4_STSD;
+ break;
+ default:
+ break;
+ }
+
+ /* frame related */
+ exp_size += ENTRY_SIZE_VIDEO_ST + (ENTRY_SIZE_VIDEO_STTS * nb_video_stts_entry) +
+ (ENTRY_SIZE_VIDEO_STSS * nb_video_i_frames) + (ENTRY_SIZE_STSC) +
+ ((ENTRY_SIZE_VIDEO_STSZ + ENTRY_SIZE_VIDEO_STCO) * nb_video_frames);
+
+ qtmux->video_expected_trailer_size = exp_size;
+ }
+
+ /* Calculate trailer size for audio stream */
+ if (audio_stream) {
+ exp_size += MUX_COMMON_SIZE_AUDIO_HEADER + ENTRY_SIZE_AUDIO_ST + (ENTRY_SIZE_AUDIO_STTS * nb_audio_stts_entry) +
+ (ENTRY_SIZE_STSC) + ((ENTRY_SIZE_AUDIO_STSZ + ENTRY_SIZE_AUDIO_STCO) * nb_audio_frames);
+
+ if (pad->fourcc == FOURCC_samr)
+ exp_size += ENTRY_SIZE_AUDIO_AMR_STSD;
+ else
+ exp_size += ENTRY_SIZE_AUDIO_AAC_STSD;
+
+ qtmux->audio_expected_trailer_size = exp_size;
+ }
+
+ qtmux->expected_trailer_size += qtmux->video_expected_trailer_size + qtmux->audio_expected_trailer_size;
+
+ /*
+ GST_INFO_OBJECT (qtmux, "pad type %s", GST_PAD_NAME(pad->collect.pad));
+ GST_INFO_OBJECT (qtmux, "VIDEO : stts-entry=[%d], i-frame=[%d], video-sample=[%d]", nb_video_stts_entry, nb_video_i_frames, nb_video_frames);
+ GST_INFO_OBJECT (qtmux, "AUDIO : stts-entry=[%d], audio-sample=[%d]", nb_audio_stts_entry, nb_audio_frames);
+ GST_INFO_OBJECT (qtmux, "expected trailer size %d", qtmux->expected_trailer_size);
+ */
+
+ return;
+}
+#endif /* TIZEN_FEATURE_GST_MUX_ENHANCEMENT */
+
static void
gst_qt_mux_base_init (gpointer g_class)
{
GParamFlags streamable_flags;
const gchar *streamable_desc;
gboolean streamable;
+#ifdef TIZEN_FEATURE_GST_MUX_ENHANCEMENT
+ GParamSpec *tspec = NULL;
+#endif /* TIZEN_FEATURE_GST_MUX_ENHANCEMENT */
#define STREAMABLE_DESC "If set to true, the output should be as if it is to "\
"be streamed and hence no indexes written or duration written."
"Maximum allowed drift of raw audio samples vs. timestamps in nanoseconds",
0, G_MAXUINT64, DEFAULT_MAX_RAW_AUDIO_DRIFT,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_START_GAP_THRESHOLD,
+ g_param_spec_uint64 ("start-gap-threshold", "Start Gap Threshold",
+ "Threshold for creating an edit list for gaps at the start in nanoseconds",
+ 0, G_MAXUINT64, DEFAULT_START_GAP_THRESHOLD,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+#ifdef TIZEN_FEATURE_GST_MUX_ENHANCEMENT
+ tspec = g_param_spec_uint("expected-trailer-size", "Expected Trailer Size",
+ "Expected trailer size (bytes)",
+ 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ if (tspec)
+ g_object_class_install_property(gobject_class, PROP_EXPECTED_TRAILER_SIZE, tspec);
+ else
+ GST_ERROR("g_param_spec failed for \"expected-trailer-size\"");
+#endif /* TIZEN_FEATURE_GST_MUX_ENHANCEMENT */
gstelement_class->request_new_pad =
GST_DEBUG_FUNCPTR (gst_qt_mux_request_new_pad);
if (alloc) {
qtmux->moov = atom_moov_new (qtmux->context);
+#ifndef TIZEN_FEATURE_GST_MUX_ENHANCEMENT
/* ensure all is as nice and fresh as request_new_pad would provide it */
for (walk = qtmux->sinkpads; walk; walk = g_slist_next (walk)) {
GstQTPad *qtpad = (GstQTPad *) walk->data;
qtpad->trak = atom_trak_new (qtmux->context);
atom_moov_add_trak (qtmux->moov, qtpad->trak);
}
+#endif
}
qtmux->current_pad = NULL;
qtmux->last_moov_update = GST_CLOCK_TIME_NONE;
qtmux->muxed_since_last_update = 0;
qtmux->reserved_duration_remaining = GST_CLOCK_TIME_NONE;
+
+#ifdef TIZEN_FEATURE_GST_MUX_ENHANCEMENT
+ qtmux->expected_trailer_size = 0;
+ qtmux->video_expected_trailer_size = 0;
+ qtmux->audio_expected_trailer_size = 0;
+#endif /* TIZEN_FEATURE_GST_MUX_ENHANCEMENT */
}
static void
qtmux->interleave_bytes = DEFAULT_INTERLEAVE_BYTES;
qtmux->interleave_time = DEFAULT_INTERLEAVE_TIME;
qtmux->max_raw_audio_drift = DEFAULT_MAX_RAW_AUDIO_DRIFT;
+ qtmux->start_gap_threshold = DEFAULT_START_GAP_THRESHOLD;
/* always need this */
qtmux->context =
}
static gsize
-extract_608_field_from_cc_data (const guint8 * ccdata, gsize ccdata_size,
+extract_608_field_from_s334_1a (const guint8 * ccdata, gsize ccdata_size,
guint field, guint8 ** res)
{
guint8 *storage;
/* Iterate over the ccdata and put the corresponding tuples for the given field
* in the storage */
for (i = 0; i < ccdata_size; i += 3) {
- if ((field == 1 && ccdata[i * 3] == 0xfc) ||
- (field == 2 && ccdata[i * 3] == 0xfd)) {
+ if ((field == 1 && (ccdata[i * 3] & 0x80)) ||
+ (field == 2 && !(ccdata[i * 3] & 0x80))) {
GST_DEBUG ("Storing matching cc for field %d : 0x%02x 0x%02x", field,
ccdata[i * 3 + 1], ccdata[i * 3 + 2]);
if (res_size >= storage_size) {
gsize write_offs = 0;
cdat_size =
- extract_608_field_from_cc_data (inmap.data, inmap.size, 1, &cdat);
+ extract_608_field_from_s334_1a (inmap.data, inmap.size, 1, &cdat);
cdt2_size =
- extract_608_field_from_cc_data (inmap.data, inmap.size, 2, &cdt2);
+ extract_608_field_from_s334_1a (inmap.data, inmap.size, 2, &cdt2);
if (cdat_size)
total_size += cdat_size + 8;
case FOURCC_c708:
{
/* Take the whole CDP */
- if (in_prefill && size > 92) {
+ if (in_prefill && size > 256) {
GST_ERROR_OBJECT (qtmux, "Input C708 CDP too big for prefill mode !");
break;
}
- newbuf = gst_buffer_new_and_alloc (in_prefill ? 100 : size + 8);
+ newbuf = gst_buffer_new_and_alloc (in_prefill ? 256 + 8 : size + 8);
/* Let's copy over all metadata and not the memory */
gst_buffer_copy_into (newbuf, buf, GST_BUFFER_COPY_METADATA, 0, size);
/* We always write both cdat and cdt2 atom in prefill mode */
return 20;
case FOURCC_c708:
- /* We're cheating a bit by always allocating 100bytes even if we use less */
- return 100;
+ /* We're cheating a bit by always allocating 256 bytes plus 8 bytes for the atom header
+ * even if we use less */
+ return 256 + 8;
case FOURCC_sowt:
case FOURCC_twos:{
guint64 block_idx;
gst_collect_pads_peek (qtmux->collect, (GstCollectData *) qpad);
GstVideoTimeCodeMeta *tc_meta;
- if (buffer && (tc_meta = gst_buffer_get_video_time_code_meta (buffer))) {
+ if (buffer && (tc_meta = gst_buffer_get_video_time_code_meta (buffer))
+ && qpad->trak->is_video) {
GstVideoTimeCode *tc = &tc_meta->tc;
qpad->tc_trak = atom_trak_new (qtmux->context);
(NULL));
return GST_FLOW_ERROR;
}
+ if (qtmux->reserved_moov_update_period == GST_CLOCK_TIME_NONE) {
+ GST_WARNING_OBJECT (qtmux,
+ "Robust muxing requires reserved-moov-update-period to be set");
+ }
break;
case GST_QT_MUX_MODE_FAST_START:
case GST_QT_MUX_MODE_FRAGMENTED_STREAMABLE:
qtmux->timescale = suggested_timescale;
}
+ /* Set width/height/timescale of any closed caption tracks to that of the
+ * first video track */
+ {
+ guint video_width = 0, video_height = 0;
+ guint32 video_timescale = 0;
+ GSList *walk;
+
+ for (walk = qtmux->sinkpads; walk; walk = g_slist_next (walk)) {
+ GstCollectData *cdata = (GstCollectData *) walk->data;
+ GstQTPad *qpad = (GstQTPad *) cdata;
+
+ if (!qpad->trak)
+ continue;
+
+ /* Not closed caption */
+ if (qpad->trak->mdia.hdlr.handler_type != FOURCC_clcp)
+ continue;
+
+ if (video_width == 0 || video_height == 0 || video_timescale == 0) {
+ GSList *walk2;
+
+ for (walk2 = qtmux->sinkpads; walk2; walk2 = g_slist_next (walk2)) {
+ GstCollectData *cdata2 = (GstCollectData *) walk2->data;
+ GstQTPad *qpad2 = (GstQTPad *) cdata2;
+
+ if (!qpad2->trak)
+ continue;
+
+ /* not video */
+ if (!qpad2->trak->mdia.minf.vmhd)
+ continue;
+
+ video_width = qpad2->trak->tkhd.width;
+ video_height = qpad2->trak->tkhd.height;
+ video_timescale = qpad2->trak->mdia.mdhd.time_info.timescale;
+ }
+ }
+
+ qpad->trak->tkhd.width = video_width << 16;
+ qpad->trak->tkhd.height = video_height << 16;
+ qpad->trak->mdia.mdhd.time_info.timescale = video_timescale;
+ }
+ }
+
/* initialize our moov recovery file */
if (qtmux->moov_recov_file_path) {
gst_qt_mux_prepare_moov_recovery (qtmux);
has_gap = (qtpad->first_ts > (qtmux->first_ts + qtpad->dts_adjustment));
if (has_gap) {
- GstClockTime diff;
+ GstClockTime diff, trak_lateness;
diff = qtpad->first_ts - (qtmux->first_ts + qtpad->dts_adjustment);
lateness = gst_util_uint64_scale_round (diff,
qtmux->timescale, GST_SECOND);
- if (lateness > 0) {
+ /* Allow up to 1 trak timescale unit of lateness, Such a small
+ * timestamp/duration can't be represented by the trak-specific parts
+ * of the headers anyway, so it's irrelevantly small */
+ trak_lateness = gst_util_uint64_scale (diff,
+ atom_trak_get_timescale (qtpad->trak), GST_SECOND);
+
+ if (trak_lateness > 0 && diff > qtmux->start_gap_threshold) {
GST_DEBUG_OBJECT (qtmux,
"Pad %s is a late stream by %" GST_TIME_FORMAT,
GST_PAD_NAME (qtpad->collect.pad), GST_TIME_ARGS (diff));
* 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 (qtmux->last_dts, qtmux->timescale, GST_SECOND);
- GST_DEBUG_OBJECT (qtmux, "rewriting moov with mvex duration %"
- GST_TIME_FORMAT, GST_TIME_ARGS (qtmux->last_dts));
+ gst_util_uint64_scale_round (qtmux->last_dts, qtmux->timescale,
+ GST_SECOND);
+ GST_DEBUG_OBJECT (qtmux,
+ "rewriting moov with 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;
for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
GstCollectData *cdata = (GstCollectData *) walk->data;
GstQTPad *qpad = (GstQTPad *) cdata;
- const TrakBufferEntryInfo *sample_entry;
guint64 block_idx;
AtomSTBL *stbl = &qpad->trak->mdia.minf.stbl;
/* Get the block index of the last sample we wrote, not of the next
* sample we would write */
block_idx = prefill_get_block_index (qtmux, qpad);
- g_assert (block_idx > 0);
- block_idx--;
-
- sample_entry =
- &g_array_index (qpad->samples, TrakBufferEntryInfo, block_idx);
/* stts */
- {
+ if (block_idx > 0) {
STTSEntry *entry;
guint64 nsamples = 0;
gint i, n;
nsamples += entry->sample_count;
}
g_assert (i < n);
+ } else {
+ stbl->stts.entries.len = 0;
}
/* stsz */
gint i, n;
guint64 nsamples = 0;
gint chunk_index = 0;
-
- n = stbl->stco64.entries.len;
- for (i = 0; i < n; i++) {
- guint64 *entry = &atom_array_index (&stbl->stco64.entries, i);
-
- if (*entry == sample_entry->chunk_offset) {
- stbl->stco64.entries.len = i + 1;
- chunk_index = i + 1;
- break;
+ const TrakBufferEntryInfo *sample_entry;
+
+ if (block_idx > 0) {
+ sample_entry =
+ &g_array_index (qpad->samples, TrakBufferEntryInfo,
+ block_idx - 1);
+
+ n = stbl->stco64.entries.len;
+ for (i = 0; i < n; i++) {
+ guint64 *entry = &atom_array_index (&stbl->stco64.entries, i);
+
+ if (*entry == sample_entry->chunk_offset) {
+ stbl->stco64.entries.len = i + 1;
+ chunk_index = i + 1;
+ break;
+ }
}
- }
- g_assert (i < n);
- g_assert (chunk_index > 0);
-
- n = stbl->stsc.entries.len;
- for (i = 0; i < n; i++) {
- STSCEntry *entry = &atom_array_index (&stbl->stsc.entries, i);
-
- if (entry->first_chunk >= chunk_index)
- break;
+ g_assert (i < n);
+ g_assert (chunk_index > 0);
+
+ n = stbl->stsc.entries.len;
+ for (i = 0; i < n; i++) {
+ STSCEntry *entry = &atom_array_index (&stbl->stsc.entries, i);
+
+ if (entry->first_chunk >= chunk_index)
+ break;
+
+ if (i > 0) {
+ nsamples +=
+ (entry->first_chunk - atom_array_index (&stbl->stsc.entries,
+ i -
+ 1).first_chunk) * atom_array_index (&stbl->stsc.entries,
+ i - 1).samples_per_chunk;
+ }
+ }
+ g_assert (i <= n);
if (i > 0) {
+ STSCEntry *prev_entry =
+ &atom_array_index (&stbl->stsc.entries, i - 1);
nsamples +=
- (entry->first_chunk - atom_array_index (&stbl->stsc.entries,
- i -
- 1).first_chunk) * atom_array_index (&stbl->stsc.entries,
- i - 1).samples_per_chunk;
- }
- }
- g_assert (i <= n);
-
- if (i > 0) {
- STSCEntry *prev_entry =
- &atom_array_index (&stbl->stsc.entries, i - 1);
- nsamples +=
- (chunk_index -
- prev_entry->first_chunk) * prev_entry->samples_per_chunk;
- if (qpad->sample_offset - nsamples > 0) {
- stbl->stsc.entries.len = i;
- atom_stsc_add_new_entry (&stbl->stsc, chunk_index,
- qpad->sample_offset - nsamples);
+ (chunk_index -
+ prev_entry->first_chunk) * prev_entry->samples_per_chunk;
+ if (qpad->sample_offset - nsamples > 0) {
+ stbl->stsc.entries.len = i;
+ atom_stsc_add_new_entry (&stbl->stsc, chunk_index,
+ qpad->sample_offset - nsamples);
+ } else {
+ stbl->stsc.entries.len = i;
+ stbl->stco64.entries.len--;
+ }
} else {
- stbl->stsc.entries.len = i;
- stbl->stco64.entries.len--;
+ /* Everything in a single chunk */
+ stbl->stsc.entries.len = 0;
+ atom_stsc_add_new_entry (&stbl->stsc, chunk_index,
+ qpad->sample_offset);
}
} else {
- /* Everything in a single chunk */
+ stbl->stco64.entries.len = 0;
stbl->stsc.entries.len = 0;
- atom_stsc_add_new_entry (&stbl->stsc, chunk_index,
- qpad->sample_offset);
}
}
GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
("Unexpected values in sample %" G_GUINT64_FORMAT,
pad->sample_offset + 1));
+ GST_ERROR_OBJECT (qtmux, "Expected: samples %u, delta %u, size %u, "
+ "chunk offset %" G_GUINT64_FORMAT ", "
+ "pts offset %" G_GUINT64_FORMAT ", sync %d",
+ sample_entry->nsamples,
+ sample_entry->delta,
+ sample_entry->size,
+ sample_entry->chunk_offset,
+ sample_entry->pts_offset, sample_entry->sync);
+ GST_ERROR_OBJECT (qtmux, "Got: samples %u, delta %u, size %u, "
+ "chunk offset %" G_GUINT64_FORMAT ", "
+ "pts offset %" G_GUINT64_FORMAT ", sync %d",
+ nsamples,
+ (guint) scaled_duration,
+ sample_size, chunk_offset, pts_offset, sync);
+
gst_buffer_unref (buffer);
return GST_FLOW_ERROR;
}
guint32 frames_since_daily_jam;
GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
+ if (!pad->trak->is_video)
+ return ret;
+
if (qtmux_klass->format != GST_QT_MUX_FORMAT_QT)
return ret;
}
}
+#ifdef TIZEN_FEATURE_GST_MUX_ENHANCEMENT
+ gst_qt_mux_update_expected_trailer_size(qtmux, pad);
+#endif /* TIZEN_FEATURE_GST_MUX_ENHANCEMENT */
+
exit:
return ret;
} else if (strcmp (mimetype, "video/x-cineform") == 0) {
entry.fourcc = FOURCC_cfhd;
sync = FALSE;
+ } else if (strcmp (mimetype, "video/x-av1") == 0) {
+ gint presentation_delay;
+ guint8 presentation_delay_byte = 0;
+ GstBuffer *av1_codec_data;
+
+ if (gst_structure_get_int (structure, "presentation-delay",
+ &presentation_delay)) {
+ presentation_delay_byte = 1 << 5;
+ presentation_delay_byte |= MAX (0xF, presentation_delay & 0xF);
+ }
+
+
+ av1_codec_data = gst_buffer_new_allocate (NULL, 5, NULL);
+ /* Fill version and 3 bytes of flags to 0 */
+ gst_buffer_memset (av1_codec_data, 0, 0, 4);
+ gst_buffer_fill (av1_codec_data, 4, &presentation_delay_byte, 1);
+ if (codec_data)
+ av1_codec_data = gst_buffer_append (av1_codec_data,
+ gst_buffer_ref ((GstBuffer *) codec_data));
+
+ entry.fourcc = FOURCC_av01;
+
+ ext_atom = build_btrt_extension (0, qtpad->avg_bitrate, qtpad->max_bitrate);
+ if (ext_atom != NULL)
+ ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
+ ext_atom = build_codec_data_extension (FOURCC_av1C, av1_codec_data);
+ if (ext_atom != NULL)
+ ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
+ gst_buffer_unref (av1_codec_data);
}
if (!entry.fourcc)
structure = gst_caps_get_structure (caps, 0);
- /* We know we only handle 608,format=cc_data and 708,format=cdp */
+ /* We know we only handle 608,format=s334-1a and 708,format=cdp */
if (gst_structure_has_name (structure, "closedcaption/x-cea-608")) {
fourcc_entry = FOURCC_c608;
} else if (gst_structure_has_name (structure, "closedcaption/x-cea-708")) {
} else
goto refuse_caps;
- /* FIXME: Get the timescale from the video track ? */
+ /* We set the real timescale later to the one from the video track when
+ * writing the headers */
timescale = gst_qt_mux_pad_get_timescale (GST_QT_MUX_PAD_CAST (pad));
if (!timescale && qtmux->trak_timescale)
timescale = qtmux->trak_timescale;
(SampleTableEntry *) atom_trak_set_caption_type (qtpad->trak,
qtmux->context, timescale, fourcc_entry);
+ /* Initialize caption track language code to 0 unless something else is
+ * specified. Without this, Final Cut considers it "non-standard"
+ */
+ qtpad->trak->mdia.mdhd.language_code = 0;
+
gst_object_unref (qtmux);
return TRUE;
g_assert (qtpad);
if (qtpad->trak) {
/* https://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap4/qtff4.html */
- qtpad->trak->mdia.mdhd.language_code =
- (iso_code[0] - 0x60) * 0x400 + (iso_code[1] - 0x60) * 0x20 +
- (iso_code[2] - 0x60);
+ qtpad->trak->mdia.mdhd.language_code = language_code (iso_code);
}
}
g_free (code);
case PROP_MAX_RAW_AUDIO_DRIFT:
g_value_set_uint64 (value, qtmux->max_raw_audio_drift);
break;
+ case PROP_START_GAP_THRESHOLD:
+ g_value_set_uint64 (value, qtmux->start_gap_threshold);
+ break;
+#ifdef TIZEN_FEATURE_GST_MUX_ENHANCEMENT
+ case PROP_EXPECTED_TRAILER_SIZE:
+ g_value_set_uint(value, qtmux->expected_trailer_size);
+ break;
+#endif /* TIZEN_FEATURE_GST_MUX_ENHANCEMENT */
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
case PROP_MAX_RAW_AUDIO_DRIFT:
qtmux->max_raw_audio_drift = g_value_get_uint64 (value);
break;
+ case PROP_START_GAP_THRESHOLD:
+ qtmux->start_gap_threshold = g_value_get_uint64 (value);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;