qtmux: Added support for writing timecode track

https://bugzilla.gnome.org/show_bug.cgi?id=767950
This commit is contained in:
Vivia Nikolaidou 2016-06-24 16:32:37 +03:00 committed by Sebastian Dröge
parent 578e93cd0b
commit cdb7649909
5 changed files with 650 additions and 4 deletions

View file

@ -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;
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)
{

View file

@ -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);

View file

@ -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')

View file

@ -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;

View file

@ -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;