From: Vivia Nikolaidou Date: Fri, 24 Jun 2016 13:32:37 +0000 (+0300) Subject: qtmux: Added support for writing timecode track X-Git-Tag: 1.19.3~509^2~2606 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=cdb764990965d756813ce25695bb0e1974466e0c;p=platform%2Fupstream%2Fgstreamer.git qtmux: Added support for writing timecode track https://bugzilla.gnome.org/show_bug.cgi?id=767950 --- diff --git a/gst/isomp4/atoms.c b/gst/isomp4/atoms.c index e0ec513..b2f7f74 100644 --- a/gst/isomp4/atoms.c +++ b/gst/isomp4/atoms.c @@ -398,6 +398,96 @@ atom_edts_free (AtomEDTS * edts) } static void +atom_tcmi_init (AtomTCMI * tcmi) +{ + guint8 flags[3] = { 0, 0, 0 }; + + atom_full_init (&tcmi->header, FOURCC_tcmi, 0, 0, 0, flags); +} + +static void +atom_tcmi_clear (AtomTCMI * tcmi) +{ + atom_full_clear (&tcmi->header); + tcmi->text_font = 0; + tcmi->text_face = 0; + tcmi->text_size = 0; + tcmi->text_color[0] = 0; + tcmi->text_color[1] = 0; + tcmi->text_color[2] = 0; + tcmi->bg_color[0] = 0; + tcmi->bg_color[1] = 0; + tcmi->bg_color[2] = 0; + g_free (tcmi->font_name); + tcmi->font_name = NULL; +} + +static void +atom_tmcd_init (AtomTMCD * tmcd) +{ + atom_header_set (&tmcd->header, FOURCC_tmcd, 0, 0); + atom_tcmi_init (&tmcd->tcmi); +} + +static void +atom_tmcd_clear (AtomTMCD * tmcd) +{ + atom_clear (&tmcd->header); + atom_tcmi_clear (&tmcd->tcmi); +} + +static void +atom_gmin_init (AtomGMIN * gmin) +{ + guint8 flags[3] = { 0, 0, 0 }; + + atom_full_init (&gmin->header, FOURCC_gmin, 0, 0, 0, flags); +} + +static void +atom_gmin_clear (AtomGMIN * gmin) +{ + atom_full_clear (&gmin->header); + gmin->graphics_mode = 0; + gmin->opcolor[0] = 0; + gmin->opcolor[1] = 0; + gmin->opcolor[2] = 0; + gmin->balance = 0; + gmin->reserved = 0; +} + +static void +atom_gmhd_init (AtomGMHD * gmhd) +{ + atom_header_set (&gmhd->header, FOURCC_gmhd, 0, 0); + atom_gmin_init (&gmhd->gmin); + atom_tmcd_init (&gmhd->tmcd); +} + +static void +atom_gmhd_clear (AtomGMHD * gmhd) +{ + atom_clear (&gmhd->header); + atom_gmin_clear (&gmhd->gmin); + atom_tmcd_clear (&gmhd->tmcd); +} + +static AtomGMHD * +atom_gmhd_new (void) +{ + AtomGMHD *gmhd = g_new0 (AtomGMHD, 1); + atom_gmhd_init (gmhd); + return gmhd; +} + +static void +atom_gmhd_free (AtomGMHD * gmhd) +{ + atom_gmhd_clear (gmhd); + g_free (gmhd); +} + +static void atom_sample_entry_init (SampleTableEntry * se, guint32 type) { atom_header_set (&se->header, type, 0, 0); @@ -452,6 +542,30 @@ sample_entry_mp4a_free (SampleTableEntryMP4A * mp4a) } static void +sample_entry_tmcd_init (SampleTableEntryTMCD * tmcd) +{ + atom_sample_entry_init (&tmcd->se, FOURCC_tmcd); + + tmcd->tc_flags = 0; + tmcd->timescale = 0; + tmcd->frame_duration = 0; + tmcd->n_frames = 0; + + tmcd->name.language_code = 0; + g_free (tmcd->name.name); + tmcd->name.name = NULL; +} + +static SampleTableEntryTMCD * +sample_entry_tmcd_new (void) +{ + SampleTableEntryTMCD *tmcd = g_new0 (SampleTableEntryTMCD, 1); + + sample_entry_tmcd_init (tmcd); + return tmcd; +} + +static void sample_entry_mp4v_init (SampleTableEntryMP4V * mp4v, AtomsContext * context) { atom_sample_entry_init (&mp4v->se, FOURCC_mp4v); @@ -934,6 +1048,7 @@ atom_minf_init (AtomMINF * minf, AtomsContext * context) minf->vmhd = NULL; minf->smhd = NULL; minf->hmhd = NULL; + minf->gmhd = NULL; if (context->flavor == ATOMS_TREE_FLAVOR_MOV) { minf->hdlr = atom_hdlr_new (context); @@ -961,6 +1076,10 @@ atom_minf_clear_handlers (AtomMINF * minf) atom_hmhd_free (minf->hmhd); minf->hmhd = NULL; } + if (minf->gmhd) { + atom_gmhd_free (minf->gmhd); + minf->gmhd = NULL; + } } static void @@ -1140,6 +1259,40 @@ atom_udta_clear (AtomUDTA * udta) atom_info_list_free (udta->entries); } +static void +atom_tref_init (AtomTREF * tref, guint32 reftype) +{ + atom_header_set (&tref->header, FOURCC_tref, 0, 0); + tref->reftype = reftype; + atom_array_init (&tref->entries, 128); +} + +static void +atom_tref_clear (AtomTREF * tref) +{ + atom_clear (&tref->header); + tref->reftype = 0; + atom_array_clear (&tref->entries); +} + +AtomTREF * +atom_tref_new (guint32 reftype) +{ + AtomTREF *tref; + + tref = g_new0 (AtomTREF, 1); + atom_tref_init (tref, reftype); + + return tref; +} + +static void +atom_tref_free (AtomTREF * tref) +{ + atom_tref_clear (tref); + g_free (tref); +} + /* Clear added tags, but keep the context/flavor the same */ void atom_udta_clear_tags (AtomUDTA * udta) @@ -1255,6 +1408,7 @@ atom_trak_init (AtomTRAK * trak, AtomsContext * context) atom_udta_init (&trak->udta, context); trak->edts = NULL; atom_mdia_init (&trak->mdia, context); + trak->tref = NULL; } AtomTRAK * @@ -1275,6 +1429,8 @@ atom_trak_clear (AtomTRAK * trak) atom_edts_free (trak->edts); atom_udta_clear (&trak->udta); atom_mdia_clear (&trak->mdia); + if (trak->tref) + atom_tref_free (trak->tref); } static void @@ -1651,6 +1807,91 @@ atom_hmhd_copy_data (AtomHMHD * hmhd, guint8 ** buffer, guint64 * size, return original_offset - *offset; } +static guint64 +atom_tcmi_copy_data (AtomTCMI * tcmi, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_full_copy_data (&tcmi->header, buffer, size, offset)) { + return 0; + } + prop_copy_uint16 (tcmi->text_font, buffer, size, offset); + prop_copy_uint16 (tcmi->text_face, buffer, size, offset); + prop_copy_uint16 (tcmi->text_size, buffer, size, offset); + prop_copy_uint16 (tcmi->text_color[0], buffer, size, offset); + prop_copy_uint16 (tcmi->text_color[1], buffer, size, offset); + prop_copy_uint16 (tcmi->text_color[2], buffer, size, offset); + prop_copy_uint16 (tcmi->bg_color[0], buffer, size, offset); + prop_copy_uint16 (tcmi->bg_color[1], buffer, size, offset); + prop_copy_uint16 (tcmi->bg_color[2], buffer, size, offset); + /* reserved */ + prop_copy_uint16 (0, buffer, size, offset); + prop_copy_size_string ((guint8 *) tcmi->font_name, strlen (tcmi->font_name), + buffer, size, offset); + + atom_write_size (buffer, size, offset, original_offset); + return original_offset - *offset; +} + +static guint64 +atom_tmcd_copy_data (AtomTMCD * tmcd, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_copy_data (&tmcd->header, buffer, size, offset)) { + return 0; + } + if (!atom_tcmi_copy_data (&tmcd->tcmi, buffer, size, offset)) { + return 0; + } + + atom_write_size (buffer, size, offset, original_offset); + return original_offset - *offset; +} + +static guint64 +atom_gmin_copy_data (AtomGMIN * gmin, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_full_copy_data (&gmin->header, buffer, size, offset)) { + return 0; + } + prop_copy_uint16 (gmin->graphics_mode, buffer, size, offset); + prop_copy_uint16 (gmin->opcolor[0], buffer, size, offset); + prop_copy_uint16 (gmin->opcolor[1], buffer, size, offset); + prop_copy_uint16 (gmin->opcolor[2], buffer, size, offset); + prop_copy_uint8 (gmin->balance, buffer, size, offset); + /* reserved */ + prop_copy_uint8 (0, buffer, size, offset); + + atom_write_size (buffer, size, offset, original_offset); + return original_offset - *offset; +} + +static guint64 +atom_gmhd_copy_data (AtomGMHD * gmhd, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_copy_data (&gmhd->header, buffer, size, offset)) { + return 0; + } + if (!atom_gmin_copy_data (&gmhd->gmin, buffer, size, offset)) { + return 0; + } + if (!atom_tmcd_copy_data (&gmhd->tmcd, buffer, size, offset)) { + return 0; + } + + atom_write_size (buffer, size, offset, original_offset); + return original_offset - *offset; +} + static gboolean atom_url_same_file_flag (AtomURL * url) { @@ -1884,6 +2125,44 @@ sample_entry_tx3g_copy_data (SampleTableEntryTX3G * tx3g, guint8 ** buffer, return *offset - original_offset; } +static guint64 +sample_entry_tmcd_copy_data (SampleTableEntryTMCD * tmcd, guint8 ** buffer, + guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_sample_entry_copy_data (&tmcd->se, buffer, size, offset)) { + return 0; + } + + /* reserved */ + prop_copy_uint32 (0, buffer, size, offset); + + prop_copy_uint32 (tmcd->tc_flags, buffer, size, offset); + prop_copy_uint32 (tmcd->timescale, buffer, size, offset); + prop_copy_uint32 (tmcd->frame_duration, buffer, size, offset); + prop_copy_uint8 (tmcd->n_frames, buffer, size, offset); + + /* reserved */ + prop_copy_uint8 (0, buffer, size, offset); + { + Atom atom; + guint64 name_offset = *offset; + + atom_header_set (&atom, FOURCC_name, 0, 0); + atom_copy_data (&atom, buffer, size, offset); + prop_copy_uint16 (strlen (tmcd->name.name), buffer, size, offset); + prop_copy_uint16 (tmcd->name.language_code, buffer, size, offset); + prop_copy_fixed_size_string ((guint8 *) tmcd->name.name, + strlen (tmcd->name.name), buffer, size, offset); + + atom_write_size (buffer, size, offset, name_offset); + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + guint64 atom_stsz_copy_data (AtomSTSZ * stsz, guint8 ** buffer, guint64 * size, guint64 * offset) @@ -2074,6 +2353,11 @@ atom_stsd_copy_data (AtomSTSD * stsd, guint8 ** buffer, guint64 * size, walker->data, buffer, size, offset)) { return 0; } + } else if (se->kind == TIMECODE) { + if (!sample_entry_tmcd_copy_data ((SampleTableEntryTMCD *) + walker->data, buffer, size, offset)) { + return 0; + } } else { if (!atom_hint_sample_entry_copy_data ( (AtomHintSampleEntry *) walker->data, buffer, size, offset)) { @@ -2205,6 +2489,10 @@ atom_minf_copy_data (AtomMINF * minf, guint8 ** buffer, guint64 * size, if (!atom_hmhd_copy_data (minf->hmhd, buffer, size, offset)) { return 0; } + } else if (minf->gmhd) { + if (!atom_gmhd_copy_data (minf->gmhd, buffer, size, offset)) { + return 0; + } } if (minf->hdlr) { @@ -2294,6 +2582,34 @@ atom_elst_copy_data (AtomELST * elst, guint8 ** buffer, guint64 * size, } static guint64 +atom_tref_copy_data (AtomTREF * tref, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + guint i; + + g_assert (atom_array_get_len (&tref->entries) > 0); + + if (!atom_copy_data (&tref->header, buffer, size, offset)) { + return 0; + } + + prop_copy_uint32 (8 + 4 * atom_array_get_len (&tref->entries), buffer, size, + offset); + prop_copy_fourcc (tref->reftype, buffer, size, offset); + /* minimize realloc */ + prop_copy_ensure_buffer (buffer, size, offset, + 4 * atom_array_get_len (&tref->entries)); + for (i = 0; i < atom_array_get_len (&tref->entries); i++) { + prop_copy_uint32 (atom_array_index (&tref->entries, i), buffer, size, + offset); + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 atom_edts_copy_data (AtomEDTS * edts, guint8 ** buffer, guint64 * size, guint64 * offset) { @@ -2489,6 +2805,14 @@ atom_trak_copy_data (AtomTRAK * trak, guint8 ** buffer, guint64 * size, return 0; } } + if (trak->tref) { + /* Make sure we need this atom (there is a referenced track */ + if (atom_array_get_len (&trak->tref->entries) > 0) { + if (!atom_tref_copy_data (trak->tref, buffer, size, offset)) { + return 0; + } + } + } if (!atom_mdia_copy_data (&trak->mdia, buffer, size, offset)) { return 0; @@ -2628,6 +2952,12 @@ atom_stco64_add_entry (AtomSTCO64 * stco64, guint64 entry) stco64->header.header.type = FOURCC_co64; } +void +atom_tref_add_entry (AtomTREF * tref, guint32 sample) +{ + atom_array_append (&tref->entries, sample, 512); +} + static void atom_stss_add_entry (AtomSTSS * stss, guint32 sample) { @@ -2789,6 +3119,36 @@ atom_trak_update_duration (AtomTRAK * trak, guint64 moov_timescale) } } +static void +timecode_atom_trak_set_duration (AtomTRAK * trak, guint64 duration, + guint64 timescale) +{ + STTSEntry *entry; + GList *iter; + + /* Sanity checks to ensure we have a timecode */ + g_assert (trak->mdia.minf.gmhd != NULL); + g_assert (atom_array_get_len (&trak->mdia.minf.stbl.stts.entries) == 1); + + trak->tkhd.duration = duration; + trak->mdia.mdhd.time_info.duration = duration; + trak->mdia.mdhd.time_info.timescale = timescale; + + entry = &atom_array_index (&trak->mdia.minf.stbl.stts.entries, 0); + entry->sample_delta = duration; + + for (iter = trak->mdia.minf.stbl.stsd.entries; iter; + iter = g_list_next (iter)) { + SampleTableEntry *entry = iter->data; + if (entry->kind == TIMECODE) { + SampleTableEntryTMCD *tmcd = (SampleTableEntryTMCD *) entry; + + tmcd->frame_duration = tmcd->frame_duration * timescale / tmcd->timescale; + tmcd->timescale = timescale; + } + } +} + static guint32 atom_moov_get_timescale (AtomMOOV * moov) { @@ -2810,10 +3170,23 @@ atom_moov_update_duration (AtomMOOV * moov) while (traks) { AtomTRAK *trak = (AtomTRAK *) traks->data; - atom_trak_update_duration (trak, atom_moov_get_timescale (moov)); - dur = atom_trak_get_duration (trak); - if (dur > duration) - duration = dur; + /* Skip timecodes for now: they have a placeholder duration */ + if (trak->mdia.minf.gmhd == NULL) { + atom_trak_update_duration (trak, atom_moov_get_timescale (moov)); + dur = atom_trak_get_duration (trak); + if (dur > duration) + duration = dur; + } + traks = g_list_next (traks); + } + /* Now update the duration of the timecodes */ + traks = moov->traks; + while (traks) { + AtomTRAK *trak = (AtomTRAK *) traks->data; + + if (trak->mdia.minf.gmhd != NULL) + timecode_atom_trak_set_duration (trak, duration, + atom_moov_get_timescale (moov)); traks = g_list_next (traks); } moov->mvhd.time_info.duration = duration; @@ -3323,6 +3696,34 @@ atom_trak_add_audio_entry (AtomTRAK * trak, AtomsContext * context, return mp4a; } +static SampleTableEntryTMCD * +atom_trak_add_timecode_entry (AtomTRAK * trak, AtomsContext * context, + GstVideoTimeCode * tc) +{ + AtomSTSD *stsd = &trak->mdia.minf.stbl.stsd; + SampleTableEntryTMCD *tmcd = sample_entry_tmcd_new (); + + trak->mdia.hdlr.component_type = FOURCC_mhlr; + trak->mdia.hdlr.handler_type = FOURCC_tmcd; + trak->mdia.hdlr.name = g_strdup ("Time Code Media Handler"); + trak->mdia.mdhd.time_info.timescale = tc->config.fps_n / tc->config.fps_d; + + tmcd->se.kind = TIMECODE; + tmcd->se.data_reference_index = 1; + tmcd->tc_flags = TC_24H_MAX; + if (tc->config.flags &= GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) + tmcd->tc_flags |= TC_DROP_FRAME; + tmcd->name.language_code = 0; + tmcd->name.name = g_strdup ("Tape"); + tmcd->timescale = tc->config.fps_n; + tmcd->frame_duration = tc->config.fps_d; + tmcd->n_frames = tc->config.fps_n / tc->config.fps_d; + + stsd->entries = g_list_prepend (stsd->entries, tmcd); + stsd->n_entries++; + return tmcd; +} + static SampleTableEntryMP4V * atom_trak_add_video_entry (AtomTRAK * trak, AtomsContext * context, guint32 type) @@ -3467,6 +3868,34 @@ atom_trak_set_audio_type (AtomTRAK * trak, AtomsContext * context, return ste; } +SampleTableEntryTMCD * +atom_trak_set_timecode_type (AtomTRAK * trak, AtomsContext * context, + GstVideoTimeCode * tc) +{ + SampleTableEntryTMCD *ste; + AtomGMHD *gmhd = trak->mdia.minf.gmhd; + + if (context->flavor != ATOMS_TREE_FLAVOR_MOV) { + return NULL; + } + + ste = atom_trak_add_timecode_entry (trak, context, tc); + + gmhd = atom_gmhd_new (); + gmhd->gmin.graphics_mode = 0x0040; + gmhd->gmin.opcolor[0] = 0x8000; + gmhd->gmin.opcolor[1] = 0x8000; + gmhd->gmin.opcolor[2] = 0x8000; + gmhd->tmcd.tcmi.text_size = 12; + gmhd->tmcd.tcmi.font_name = g_strdup ("Chicago"); /* Pascal string */ + + trak->mdia.minf.gmhd = gmhd; + trak->is_video = FALSE; + trak->is_h264 = FALSE; + + return ste; +} + static AtomInfo * build_pasp_extension (gint par_width, gint par_height) { diff --git a/gst/isomp4/atoms.h b/gst/isomp4/atoms.h index b105a4d..283d9cf 100644 --- a/gst/isomp4/atoms.h +++ b/gst/isomp4/atoms.h @@ -45,6 +45,7 @@ #include #include +#include #include "descriptors.h" #include "properties.h" @@ -281,6 +282,45 @@ typedef struct _AtomHMHD guint32 sliding_avg_bitrate; } AtomHMHD; +typedef struct _AtomTCMI +{ + AtomFull header; + + guint16 text_font; + guint16 text_face; + guint16 text_size; + guint16 text_color[3]; + guint16 bg_color[3]; + gchar *font_name; +} AtomTCMI; + +typedef struct _AtomTMCD +{ + Atom header; + + AtomTCMI tcmi; +} AtomTMCD; + +typedef struct _AtomGMIN +{ + AtomFull header; + + guint16 graphics_mode; + guint16 opcolor[3]; + guint8 balance; + guint8 reserved; + +} AtomGMIN; + +typedef struct _AtomGMHD +{ + Atom header; + + AtomGMIN gmin; + AtomTMCD tmcd; + +} AtomGMHD; + typedef struct _AtomURL { AtomFull header; @@ -342,6 +382,7 @@ typedef enum _SampleEntryKind AUDIO, VIDEO, SUBTITLE, + TIMECODE } SampleEntryKind; typedef struct _SampleTableEntry @@ -415,6 +456,27 @@ typedef struct _SampleTableEntryMP4A GList *extension_atoms; } SampleTableEntryMP4A; +typedef struct _AtomNAME +{ + Atom header; + + guint8 language_code; + gchar *name; +} AtomNAME; + +typedef struct _SampleTableEntryTMCD +{ + SampleTableEntry se; + + guint32 tc_flags; + guint32 timescale; + guint32 frame_duration; + guint8 n_frames; + + AtomNAME name; + +} SampleTableEntryTMCD; + typedef struct _SampleTableEntryTX3G { SampleTableEntry se; @@ -463,6 +525,14 @@ typedef struct _AtomSTSC ATOM_ARRAY (STSCEntry) entries; } AtomSTSC; +/* FIXME: this can support multiple tracks */ +typedef struct _AtomTREF +{ + Atom header; + + guint32 reftype; + ATOM_ARRAY (guint32) entries; +} AtomTREF; /* * used for both STCO and CO64 @@ -514,6 +584,7 @@ typedef struct _AtomMINF AtomVMHD *vmhd; AtomSMHD *smhd; AtomHMHD *hmhd; + AtomGMHD *gmhd; AtomHDLR *hdlr; AtomDINF dinf; @@ -616,6 +687,15 @@ enum TfFlags TF_DEFAULT_BASE_IS_MOOF = 0x020000 /* default-base-is-moof */ }; +/* Timecode flags */ +enum TcFlags +{ + TC_DROP_FRAME = 0x0001, /* Drop-frame timecode */ + TC_24H_MAX = 0x0002, /* Whether the timecode wraps after 24 hours */ + TC_NEGATIVE_OK = 0x0004, /* Whether negative time values are OK */ + TC_COUNTER = 0x0008 /* Whether the time value corresponds to a tape counter value */ +}; + typedef struct _AtomTRAK { Atom header; @@ -624,6 +704,7 @@ typedef struct _AtomTRAK AtomEDTS *edts; AtomMDIA mdia; AtomUDTA udta; + AtomTREF *tref; /* some helper info for structural conformity checks */ gboolean is_video; @@ -937,6 +1018,9 @@ SampleTableEntryMP4V * atom_trak_set_video_type (AtomTRAK * trak, AtomsContext * SampleTableEntryTX3G * atom_trak_set_subtitle_type (AtomTRAK * trak, AtomsContext * context, SubtitleSampleEntry * entry); +SampleTableEntryTMCD * +atom_trak_set_timecode_type (AtomTRAK * trak, AtomsContext * context, GstVideoTimeCode * tc); + void atom_trak_update_bitrates (AtomTRAK * trak, guint32 avg_bitrate, guint32 max_bitrate); @@ -997,6 +1081,9 @@ void atom_udta_add_3gp_tag (AtomUDTA *udta, guint32 fourcc, guint8 * d void atom_udta_add_xmp_tags (AtomUDTA *udta, GstBuffer * xmp); +AtomTREF * atom_tref_new (guint32 reftype); +void atom_tref_add_entry (AtomTREF * tref, guint32 sample); + #define GST_QT_MUX_DEFAULT_TAG_LANGUAGE "und" /* undefined/unknown */ guint16 language_code (const char * lang); diff --git a/gst/isomp4/fourcc.h b/gst/isomp4/fourcc.h index 5bb43e9..9e7408b 100644 --- a/gst/isomp4/fourcc.h +++ b/gst/isomp4/fourcc.h @@ -215,7 +215,9 @@ G_BEGIN_DECLS #define FOURCC_subp GST_MAKE_FOURCC('s','u','b','p') #define FOURCC_subt GST_MAKE_FOURCC('s','u','b','t') #define FOURCC_text GST_MAKE_FOURCC('t','e','x','t') +#define FOURCC_tcmi GST_MAKE_FOURCC('t','c','m','i') #define FOURCC_tkhd GST_MAKE_FOURCC('t','k','h','d') +#define FOURCC_tmcd GST_MAKE_FOURCC('t','m','c','d') #define FOURCC_tmpo GST_MAKE_FOURCC('t','m','p','o') #define FOURCC_trak GST_MAKE_FOURCC('t','r','a','k') #define FOURCC_tref GST_MAKE_FOURCC('t','r','e','f') diff --git a/gst/isomp4/gstqtmux.c b/gst/isomp4/gstqtmux.c index 5cabb8f..cf6633d 100644 --- a/gst/isomp4/gstqtmux.c +++ b/gst/isomp4/gstqtmux.c @@ -504,6 +504,7 @@ gst_qt_mux_pad_reset (GstQTPad * qtpad) qtpad->total_duration = 0; qtpad->total_bytes = 0; qtpad->sparse = FALSE; + qtpad->tc_trak = NULL; qtpad->buf_head = 0; qtpad->buf_tail = 0; @@ -604,6 +605,8 @@ gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc) qtmux->last_moov_update = GST_CLOCK_TIME_NONE; qtmux->muxed_since_last_update = 0; qtmux->reserved_duration_remaining = GST_CLOCK_TIME_NONE; + qtmux->first_pts = GST_CLOCK_TIME_NONE; + qtmux->tc_pos = -1; } static void @@ -2498,6 +2501,10 @@ gst_qt_mux_update_edit_lists (GstQTMux * qtmux) duration += lateness; qtpad->trak->tkhd.duration = duration; + if (qtpad->tc_trak) { + qtpad->tc_trak->tkhd.duration = duration; + qtpad->tc_trak->mdia.mdhd.time_info.duration = duration; + } /* And possibly grow the moov duration */ if (duration > qtmux->moov->mvhd.time_info.duration) { @@ -2509,6 +2516,33 @@ gst_qt_mux_update_edit_lists (GstQTMux * qtmux) } static GstFlowReturn +gst_qt_mux_update_timecode (GstQTMux * qtmux) +{ + GstSegment segment; + GstBuffer *buf; + GstMapInfo map; + guint64 offset = qtmux->tc_pos; + + g_assert (qtmux->tc_pos != -1); + + gst_segment_init (&segment, GST_FORMAT_BYTES); + segment.start = offset; + gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment)); + + buf = gst_buffer_new_and_alloc (4); + gst_buffer_map (buf, &map, GST_MAP_WRITE); + + GST_WRITE_UINT32_BE (map.data, + gst_video_time_code_frames_since_daily_jam (qtmux->first_tc)); + gst_buffer_unmap (buf, &map); + + /* Reset this value, so the timecode won't be re-rewritten */ + qtmux->tc_pos = -1; + + return gst_qt_mux_send_buffer (qtmux, buf, &offset, FALSE); +} + +static GstFlowReturn gst_qt_mux_stop_file (GstQTMux * qtmux) { gboolean ret = GST_FLOW_OK; @@ -2528,6 +2562,13 @@ gst_qt_mux_stop_file (GstQTMux * qtmux) } gst_qt_mux_update_global_statistics (qtmux); + if (qtmux->tc_pos != -1) { + /* File is being stopped and timecode hasn't been updated. Update it now + * with whatever we have */ + ret = gst_qt_mux_update_timecode (qtmux); + if (ret != GST_FLOW_OK) + return ret; + } switch (qtmux->mux_mode) { case GST_QT_MUX_MODE_FRAGMENTED:{ @@ -2979,6 +3020,84 @@ gst_qt_mux_register_and_push_sample (GstQTMux * qtmux, GstQTPad * pad, return ret; } +static GstFlowReturn +gst_qt_mux_check_and_update_timecode (GstQTMux * qtmux, GstQTPad * pad, + GstBuffer * buf, GstFlowReturn ret) +{ + if (buf != NULL && (pad->tc_trak == NULL || qtmux->tc_pos != -1)) { + GstVideoTimeCodeMeta *tc_meta = gst_buffer_get_video_time_code_meta (buf); + if (tc_meta) { + GstVideoTimeCode *tc = &tc_meta->tc; + GstBuffer *tc_buf; + gsize szret; + guint32 frames_since_daily_jam; + + /* This means we never got a timecode before */ + if (qtmux->first_tc == NULL) { +#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); + g_free (tc_str); +#endif + g_assert (pad->tc_trak == NULL); + tc_buf = gst_buffer_new_allocate (NULL, 4, NULL); + qtmux->first_tc = gst_video_time_code_copy (tc); + /* 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 */ + if (pad->is_out_of_order) { + qtmux->first_pts = GST_BUFFER_PTS (buf); + frames_since_daily_jam = 0; + /* Position to rewrite */ + qtmux->tc_pos = qtmux->mdat_size; + } else { + frames_since_daily_jam = + gst_video_time_code_frames_since_daily_jam (qtmux->first_tc); + frames_since_daily_jam = GUINT32_TO_BE (frames_since_daily_jam); + } + /* Write the timecode trak now */ + pad->tc_trak = atom_trak_new (qtmux->context); + atom_moov_add_trak (qtmux->moov, pad->tc_trak); + + pad->trak->tref = atom_tref_new (FOURCC_tmcd); + atom_tref_add_entry (pad->trak->tref, pad->tc_trak->tkhd.track_ID); + + atom_trak_set_timecode_type (pad->tc_trak, qtmux->context, + qtmux->first_tc); + + 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); + } else if (pad->is_out_of_order) { + /* Check for a lower timecode than the one stored */ + g_assert (pad->tc_trak != NULL); + if (GST_BUFFER_DTS (buf) <= qtmux->first_pts) { + if (gst_video_time_code_compare (tc, qtmux->first_tc) == -1) { + gst_video_time_code_free (qtmux->first_tc); + qtmux->first_tc = gst_video_time_code_copy (tc); + } + } else { + guint64 bk_size = qtmux->mdat_size; + GstSegment segment; + /* If this frame's DTS is after the first PTS received, it means + * we've already received the first frame to be presented. Otherwise + * the decoder would need to go back in time */ + gst_qt_mux_update_timecode (qtmux); + + /* Reset writing position */ + gst_segment_init (&segment, GST_FORMAT_BYTES); + segment.start = bk_size; + gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment)); + } + } + } + } + return ret; +} + /* * Here we push the buffer and update the tables in the track atoms */ @@ -3025,6 +3144,8 @@ gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf) } } + ret = gst_qt_mux_check_and_update_timecode (qtmux, pad, buf, ret); + if (last_buf && !buf && !GST_BUFFER_DURATION_IS_VALID (last_buf)) { /* this is last buffer; there is no next buffer so we need valid number as duration */ last_buf = gst_buffer_make_writable (last_buf); @@ -4567,6 +4688,8 @@ gst_qt_mux_change_state (GstElement * element, GstStateChange transition) case GST_STATE_CHANGE_READY_TO_PAUSED: gst_collect_pads_start (qtmux->collect); qtmux->state = GST_QT_MUX_STATE_STARTED; + qtmux->first_tc = NULL; + qtmux->tc_pos = -1; break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING: break; diff --git a/gst/isomp4/gstqtmux.h b/gst/isomp4/gstqtmux.h index 81a23fa..5fd7df9 100644 --- a/gst/isomp4/gstqtmux.h +++ b/gst/isomp4/gstqtmux.h @@ -123,6 +123,7 @@ struct _GstQTPad /* all the atom and chunk book-keeping is delegated here * unowned/uncounted reference, parent MOOV owns */ AtomTRAK *trak; + AtomTRAK *tc_trak; SampleTableEntry *trak_ste; /* fragmented support */ /* meta data book-keeping delegated here */ @@ -205,6 +206,10 @@ struct _GstQTMux /* Set when tags are received, cleared when written to moov */ gboolean tags_changed; + /* SMPTE timecode */ + GstVideoTimeCode *first_tc; + GstClockTime first_pts; + guint64 tc_pos; /* fragmented file index */ AtomMFRA *mfra;