mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-24 08:08:22 +00:00
qtmux: Added support for writing timecode track
https://bugzilla.gnome.org/show_bug.cgi?id=767950
This commit is contained in:
parent
578e93cd0b
commit
cdb7649909
5 changed files with 650 additions and 4 deletions
|
@ -397,6 +397,96 @@ atom_edts_free (AtomEDTS * edts)
|
|||
g_free (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)
|
||||
{
|
||||
|
@ -451,6 +541,30 @@ sample_entry_mp4a_free (SampleTableEntryMP4A * mp4a)
|
|||
g_free (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)
|
||||
{
|
||||
|
@ -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) {
|
||||
|
@ -2293,6 +2581,34 @@ atom_elst_copy_data (AtomELST * elst, guint8 ** buffer, guint64 * size,
|
|||
return *offset - original_offset;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/* 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)
|
||||
{
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#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);
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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) {
|
||||
|
@ -2508,6 +2515,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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue