diff --git a/gst/isomp4/atoms.c b/gst/isomp4/atoms.c index e1441d468c..7478c37314 100644 --- a/gst/isomp4/atoms.c +++ b/gst/isomp4/atoms.c @@ -54,10 +54,11 @@ * Creates a new AtomsContext for the given flavor. */ AtomsContext * -atoms_context_new (AtomsTreeFlavor flavor) +atoms_context_new (AtomsTreeFlavor flavor, gboolean force_create_timecode_trak) { AtomsContext *context = g_new0 (AtomsContext, 1); context->flavor = flavor; + context->force_create_timecode_trak = force_create_timecode_trak; return context; } @@ -493,6 +494,34 @@ atom_gmhd_free (AtomGMHD * gmhd) g_free (gmhd); } +static void +atom_nmhd_init (AtomNMHD * nmhd) +{ + atom_header_set (&nmhd->header, FOURCC_nmhd, 0, 0); + nmhd->flags = 0; +} + +static void +atom_nmhd_clear (AtomNMHD * nmhd) +{ + atom_clear (&nmhd->header); +} + +static AtomNMHD * +atom_nmhd_new (void) +{ + AtomNMHD *nmhd = g_new0 (AtomNMHD, 1); + atom_nmhd_init (nmhd); + return nmhd; +} + +static void +atom_nmhd_free (AtomNMHD * nmhd) +{ + atom_nmhd_clear (nmhd); + g_free (nmhd); +} + static void atom_sample_entry_init (SampleTableEntry * se, guint32 type) { @@ -1129,6 +1158,10 @@ atom_minf_clear_handlers (AtomMINF * minf) atom_gmhd_free (minf->gmhd); minf->gmhd = NULL; } + if (minf->nmhd) { + atom_nmhd_free (minf->nmhd); + minf->nmhd = NULL; + } } static void @@ -1955,6 +1988,21 @@ atom_gmhd_copy_data (AtomGMHD * gmhd, guint8 ** buffer, guint64 * size, return original_offset - *offset; } +static guint64 +atom_nmhd_copy_data (AtomNMHD * nmhd, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_copy_data (&nmhd->header, buffer, size, offset)) { + return 0; + } + prop_copy_uint32 (nmhd->flags, buffer, size, offset); + + atom_write_size (buffer, size, offset, original_offset); + return original_offset - *offset; +} + static gboolean atom_url_same_file_flag (AtomURL * url) { @@ -2620,6 +2668,10 @@ atom_minf_copy_data (AtomMINF * minf, guint8 ** buffer, guint64 * size, if (!atom_gmhd_copy_data (minf->gmhd, buffer, size, offset)) { return 0; } + } else if (minf->nmhd) { + if (!atom_nmhd_copy_data (minf->nmhd, buffer, size, offset)) { + return 0; + } } if (minf->hdlr) { @@ -4076,24 +4128,36 @@ atom_trak_set_timecode_type (AtomTRAK * trak, AtomsContext * context, guint32 trak_timescale, GstVideoTimeCode * tc) { SampleTableEntryTMCD *ste; - AtomGMHD *gmhd = trak->mdia.minf.gmhd; - if (context->flavor != ATOMS_TREE_FLAVOR_MOV) { + if (context->flavor != ATOMS_TREE_FLAVOR_MOV && + !context->force_create_timecode_trak) { return NULL; } + + if (context->flavor == ATOMS_TREE_FLAVOR_MOV) { + AtomGMHD *gmhd = trak->mdia.minf.gmhd; + + 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 = atom_tmcd_new (); + gmhd->tmcd->tcmi.text_size = 12; + gmhd->tmcd->tcmi.font_name = g_strdup ("Chicago"); /* Pascal string */ + + trak->mdia.minf.gmhd = gmhd; + } else if (context->force_create_timecode_trak) { + AtomNMHD *nmhd = trak->mdia.minf.nmhd; + /* MOV files use GMHD, other files use NMHD */ + + nmhd = atom_nmhd_new (); + trak->mdia.minf.nmhd = nmhd; + } else { + return NULL; + } ste = atom_trak_add_timecode_entry (trak, context, trak_timescale, 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 = atom_tmcd_new (); - 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; diff --git a/gst/isomp4/atoms.h b/gst/isomp4/atoms.h index de93e22fda..b2587d9caf 100644 --- a/gst/isomp4/atoms.h +++ b/gst/isomp4/atoms.h @@ -103,9 +103,10 @@ typedef enum _AtomsTreeFlavor typedef struct _AtomsContext { AtomsTreeFlavor flavor; + gboolean force_create_timecode_trak; } AtomsContext; -AtomsContext* atoms_context_new (AtomsTreeFlavor flavor); +AtomsContext* atoms_context_new (AtomsTreeFlavor flavor, gboolean force_create_timecode_trak); void atoms_context_free (AtomsContext *context); #define METADATA_DATA_FLAG 0x0 @@ -325,6 +326,12 @@ typedef struct _AtomGMHD } AtomGMHD; +typedef struct _AtomNMHD +{ + Atom header; + guint32 flags; +} AtomNMHD; + typedef struct _AtomURL { AtomFull header; @@ -600,6 +607,7 @@ typedef struct _AtomMINF AtomSMHD *smhd; AtomHMHD *hmhd; AtomGMHD *gmhd; + AtomNMHD *nmhd; AtomHDLR *hdlr; AtomDINF dinf; diff --git a/gst/isomp4/fourcc.h b/gst/isomp4/fourcc.h index 689373619c..51405bb0fd 100644 --- a/gst/isomp4/fourcc.h +++ b/gst/isomp4/fourcc.h @@ -184,6 +184,7 @@ G_BEGIN_DECLS #define FOURCC_name GST_MAKE_FOURCC('n','a','m','e') #define FOURCC_nclc GST_MAKE_FOURCC('n','c','l','c') #define FOURCC_nclx GST_MAKE_FOURCC('n','c','l','x') +#define FOURCC_nmhd GST_MAKE_FOURCC('n','m','h','d') #define FOURCC_opus GST_MAKE_FOURCC('O','p','u','s') #define FOURCC_dops GST_MAKE_FOURCC('d','O','p','s') #define FOURCC_pasp GST_MAKE_FOURCC('p','a','s','p') diff --git a/gst/isomp4/gstqtmux.c b/gst/isomp4/gstqtmux.c index e5944dd690..1c0234f9ff 100644 --- a/gst/isomp4/gstqtmux.c +++ b/gst/isomp4/gstqtmux.c @@ -368,6 +368,7 @@ enum PROP_INTERLEAVE_TIME, PROP_MAX_RAW_AUDIO_DRIFT, PROP_START_GAP_THRESHOLD, + PROP_FORCE_CREATE_TIMECODE_TRAK, }; /* some spare for header size as well */ @@ -392,6 +393,7 @@ enum #define DEFAULT_INTERLEAVE_TIME 250*GST_MSECOND #define DEFAULT_MAX_RAW_AUDIO_DRIFT 40 * GST_MSECOND #define DEFAULT_START_GAP_THRESHOLD 0 +#define DEFAULT_FORCE_CREATE_TIMECODE_TRAK FALSE static void gst_qt_mux_finalize (GObject * object); @@ -635,6 +637,13 @@ gst_qt_mux_class_init (GstQTMuxClass * klass) "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)); + g_object_class_install_property (gobject_class, + PROP_FORCE_CREATE_TIMECODE_TRAK, + g_param_spec_boolean ("force-create-timecode-trak", + "Force Create Timecode Trak", + "Create a timecode trak even in unsupported flavors", + DEFAULT_FORCE_CREATE_TIMECODE_TRAK, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); gstelement_class->request_new_pad = GST_DEBUG_FUNCPTR (gst_qt_mux_request_new_pad); @@ -855,10 +864,12 @@ gst_qt_mux_init (GstQTMux * qtmux, GstQTMuxClass * qtmux_klass) qtmux->interleave_time = DEFAULT_INTERLEAVE_TIME; qtmux->max_raw_audio_drift = DEFAULT_MAX_RAW_AUDIO_DRIFT; qtmux->start_gap_threshold = DEFAULT_START_GAP_THRESHOLD; + qtmux->force_create_timecode_trak = DEFAULT_FORCE_CREATE_TIMECODE_TRAK; /* always need this */ qtmux->context = - atoms_context_new (gst_qt_mux_map_format_to_flavor (qtmux_klass->format)); + atoms_context_new (gst_qt_mux_map_format_to_flavor (qtmux_klass->format), + qtmux->force_create_timecode_trak); /* internals to initial state */ gst_qt_mux_reset (qtmux, TRUE); @@ -2862,7 +2873,8 @@ gst_qt_mux_prefill_samples (GstQTMux * qtmux) } GST_OBJECT_UNLOCK (qtmux); - if (qtmux_klass->format == GST_QT_MUX_FORMAT_QT) { + if (qtmux_klass->format == GST_QT_MUX_FORMAT_QT || + qtmux->force_create_timecode_trak) { /* For the first sample check/update timecode as needed. We do that before * all actual samples as the code in gst_qt_mux_add_buffer() does it with * initial buffer directly, not with last_buf */ @@ -3660,7 +3672,8 @@ gst_qt_mux_update_timecode (GstQTMux * qtmux, GstQTMuxPad * qtpad) guint64 offset = qtpad->tc_pos; GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux)); - if (qtmux_klass->format != GST_QT_MUX_FORMAT_QT) + if (qtmux_klass->format != GST_QT_MUX_FORMAT_QT && + !qtmux->force_create_timecode_trak) return GST_FLOW_OK; g_assert (qtpad->tc_pos != -1); @@ -4496,7 +4509,8 @@ gst_qt_mux_check_and_update_timecode (GstQTMux * qtmux, GstQTMuxPad * pad, if (!pad->trak->is_video) return ret; - if (qtmux_klass->format != GST_QT_MUX_FORMAT_QT) + if (qtmux_klass->format != GST_QT_MUX_FORMAT_QT && + !qtmux->force_create_timecode_trak) return ret; if (buf == NULL || (pad->tc_trak != NULL && pad->tc_pos == -1)) @@ -6654,6 +6668,9 @@ gst_qt_mux_get_property (GObject * object, case PROP_START_GAP_THRESHOLD: g_value_set_uint64 (value, qtmux->start_gap_threshold); break; + case PROP_FORCE_CREATE_TIMECODE_TRAK: + g_value_set_boolean (value, qtmux->force_create_timecode_trak); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -6748,6 +6765,11 @@ gst_qt_mux_set_property (GObject * object, case PROP_START_GAP_THRESHOLD: qtmux->start_gap_threshold = g_value_get_uint64 (value); break; + case PROP_FORCE_CREATE_TIMECODE_TRAK: + qtmux->force_create_timecode_trak = g_value_get_boolean (value); + qtmux->context->force_create_timecode_trak = + qtmux->force_create_timecode_trak; + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; diff --git a/gst/isomp4/gstqtmux.h b/gst/isomp4/gstqtmux.h index d18871babe..0fff736fd7 100644 --- a/gst/isomp4/gstqtmux.h +++ b/gst/isomp4/gstqtmux.h @@ -313,6 +313,8 @@ struct _GstQTMux GstClockTime start_gap_threshold; + gboolean force_create_timecode_trak; + /* for request pad naming */ guint video_pads, audio_pads, subtitle_pads, caption_pads; };