summaryrefslogtreecommitdiffstats
path: root/gst/qtmux
diff options
context:
space:
mode:
Diffstat (limited to 'gst/qtmux')
-rw-r--r--gst/qtmux/atoms.c186
-rw-r--r--gst/qtmux/atoms.h21
-rw-r--r--gst/qtmux/fourcc.h10
-rw-r--r--gst/qtmux/gstqtmux.c542
-rw-r--r--gst/qtmux/gstqtmux.h2
-rw-r--r--gst/qtmux/gstqtmuxmap.c2
6 files changed, 588 insertions, 175 deletions
diff --git a/gst/qtmux/atoms.c b/gst/qtmux/atoms.c
index 47db40ce..29859d00 100644
--- a/gst/qtmux/atoms.c
+++ b/gst/qtmux/atoms.c
@@ -1036,7 +1036,8 @@ atom_meta_free (AtomMETA * meta)
{
atom_full_clear (&meta->header);
atom_hdlr_clear (&meta->hdlr);
- atom_ilst_free (meta->ilst);
+ if (meta->ilst)
+ atom_ilst_free (meta->ilst);
meta->ilst = NULL;
g_free (meta);
}
@@ -1061,8 +1062,11 @@ static void
atom_udta_free (AtomUDTA * udta)
{
atom_clear (&udta->header);
- atom_meta_free (udta->meta);
+ if (udta->meta)
+ atom_meta_free (udta->meta);
udta->meta = NULL;
+ if (udta->entries)
+ atom_info_list_free (udta->entries);
g_free (udta);
}
@@ -1149,6 +1153,7 @@ atom_moov_init (AtomMOOV * moov, AtomsContext * context)
atom_mvhd_init (&(moov->mvhd));
moov->udta = NULL;
moov->traks = NULL;
+ moov->context = *context;
}
AtomMOOV *
@@ -1170,13 +1175,11 @@ atom_moov_free (AtomMOOV * moov)
walker = moov->traks;
while (walker) {
- GList *aux = walker;
-
+ atom_trak_free ((AtomTRAK *) walker->data);
walker = g_list_next (walker);
- moov->traks = g_list_remove_link (moov->traks, aux);
- atom_trak_free ((AtomTRAK *) aux->data);
- g_list_free (aux);
}
+ g_list_free (moov->traks);
+ moov->traks = NULL;
if (moov->udta) {
atom_udta_free (moov->udta);
@@ -1240,9 +1243,6 @@ atom_copy_data (Atom * atom, guint8 ** buffer, guint64 * size, guint64 * offset)
* would be a problem for size (re)write code, not to mention memory */
g_return_val_if_fail (atom->type == FOURCC_mdat, 0);
prop_copy_uint64 (atom->extended_size, buffer, size, offset);
- } else {
- /* just in case some trivially derived atom does not do so */
- atom_write_size (buffer, size, offset, original_offset);
}
return *offset - original_offset;
@@ -2163,6 +2163,10 @@ atom_udta_copy_data (AtomUDTA * udta, guint8 ** buffer, guint64 * size,
if (!atom_meta_copy_data (udta->meta, buffer, size, offset)) {
return 0;
}
+ } else if (udta->entries) {
+ /* extra atoms */
+ if (!atom_info_list_copy_data (udta->entries, buffer, size, offset))
+ return 0;
}
atom_write_size (buffer, size, offset, original_offset);
@@ -2238,6 +2242,10 @@ stsc_entry_new (guint32 first_chunk, guint32 samples, guint32 desc_index)
static void
atom_stsc_add_new_entry (AtomSTSC * stsc, guint32 first_chunk, guint32 nsamples)
{
+ if (stsc->entries &&
+ ((STSCEntry *) stsc->entries->data)->samples_per_chunk == nsamples)
+ return;
+
stsc->entries =
g_list_prepend (stsc->entries, stsc_entry_new (first_chunk, nsamples, 1));
stsc->n_entries++;
@@ -2517,16 +2525,18 @@ atom_moov_chunks_add_offset (AtomMOOV * moov, guint32 offset)
* Meta tags functions
*/
static void
-atom_moov_init_metatags (AtomMOOV * moov)
+atom_moov_init_metatags (AtomMOOV * moov, AtomsContext * context)
{
if (!moov->udta) {
moov->udta = atom_udta_new ();
}
- if (!moov->udta->meta) {
- moov->udta->meta = atom_meta_new ();
- }
- if (!moov->udta->meta->ilst) {
- moov->udta->meta->ilst = atom_ilst_new ();
+ if (context->flavor != ATOMS_TREE_FLAVOR_3GP) {
+ if (!moov->udta->meta) {
+ moov->udta->meta = atom_meta_new ();
+ }
+ if (!moov->udta->meta->ilst) {
+ moov->udta->meta->ilst = atom_ilst_new ();
+ }
}
}
@@ -2543,11 +2553,14 @@ atom_tag_data_alloc_data (AtomTagData * data, guint size)
static void
atom_moov_append_tag (AtomMOOV * moov, AtomInfo * tag)
{
- AtomILST *ilst;
+ GList **entries;
- atom_moov_init_metatags (moov);
- ilst = moov->udta->meta->ilst;
- ilst->entries = g_list_append (ilst->entries, tag);
+ atom_moov_init_metatags (moov, &moov->context);
+ if (moov->udta->meta)
+ entries = &moov->udta->meta->ilst->entries;
+ else
+ entries = &moov->udta->entries;
+ *entries = g_list_append (*entries, tag);
}
void
@@ -2621,6 +2634,87 @@ atom_moov_add_blob_tag (AtomMOOV * moov, guint8 * data, guint size)
atom_data_free));
}
+void
+atom_moov_add_3gp_tag (AtomMOOV * moov, guint32 fourcc, guint8 * data,
+ guint size)
+{
+ AtomData *data_atom;
+ GstBuffer *buf;
+ guint8 *bdata;
+
+ /* need full atom */
+ buf = gst_buffer_new_and_alloc (size + 4);
+ bdata = GST_BUFFER_DATA (buf);
+ /* full atom: version and flags */
+ GST_WRITE_UINT32_BE (bdata, 0);
+ memcpy (bdata + 4, data, size);
+
+ data_atom = atom_data_new_from_gst_buffer (fourcc, buf);
+ gst_buffer_unref (buf);
+
+ atom_moov_append_tag (moov,
+ build_atom_info_wrapper ((Atom *) data_atom, atom_data_copy_data,
+ atom_data_free));
+}
+
+guint16
+language_code (const char *lang)
+{
+ g_return_val_if_fail (lang != NULL, 0);
+ g_return_val_if_fail (strlen (lang) == 3, 0);
+
+ return (((lang[0] - 0x60) & 0x1F) << 10) + (((lang[1] - 0x60) & 0x1F) << 5) +
+ ((lang[2] - 0x60) & 0x1F);
+}
+
+void
+atom_moov_add_3gp_str_int_tag (AtomMOOV * moov, guint32 fourcc,
+ const gchar * value, gint16 ivalue)
+{
+ gint len = 0, size = 0;
+ guint8 *data;
+
+ if (value) {
+ len = strlen (value);
+ size = len + 3;
+ }
+
+ if (ivalue >= 0)
+ size += 2;
+
+ data = g_malloc (size + 3);
+ /* language tag and null-terminated UTF-8 string */
+ if (value) {
+ GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE));
+ /* include 0 terminator */
+ memcpy (data + 2, value, len + 1);
+ }
+ /* 16-bit unsigned int if standalone, otherwise 8-bit */
+ if (ivalue >= 0) {
+ if (size == 2)
+ GST_WRITE_UINT16_BE (data + size - 2, ivalue);
+ else {
+ GST_WRITE_UINT8 (data + size - 2, ivalue & 0xFF);
+ size--;
+ }
+ }
+
+ atom_moov_add_3gp_tag (moov, fourcc, data, size);
+ g_free (data);
+}
+
+void
+atom_moov_add_3gp_str_tag (AtomMOOV * moov, guint32 fourcc, const gchar * value)
+{
+ atom_moov_add_3gp_str_int_tag (moov, fourcc, value, -1);
+}
+
+void
+atom_moov_add_3gp_uint_tag (AtomMOOV * moov, guint32 fourcc, guint16 value)
+{
+ atom_moov_add_3gp_str_int_tag (moov, fourcc, NULL, value);
+}
+
/*
* Functions for specifying media types
*/
@@ -2804,14 +2898,56 @@ atom_trak_set_audio_type (AtomTRAK * trak, AtomsContext * context,
atom_trak_set_constant_size_samples (trak, sample_size);
}
+AtomInfo *
+build_pasp_extension (AtomTRAK * trak, gint par_width, gint par_height)
+{
+ AtomData *atom_data;
+ GstBuffer *buf;
+ guint8 *data;
+
+ buf = gst_buffer_new_and_alloc (8);
+ data = GST_BUFFER_DATA (buf);
+
+ /* ihdr = image header box */
+ GST_WRITE_UINT32_BE (data, par_width);
+ GST_WRITE_UINT32_BE (data + 4, par_height);
+
+ atom_data = atom_data_new_from_gst_buffer (FOURCC_pasp, buf);
+ gst_buffer_unref (buf);
+
+ return build_atom_info_wrapper ((Atom *) atom_data, atom_data_copy_data,
+ atom_data_free);
+}
+
void
atom_trak_set_video_type (AtomTRAK * trak, AtomsContext * context,
VisualSampleEntry * entry, guint32 scale, AtomInfo * ext)
{
SampleTableEntryMP4V *ste;
+ gint dwidth, dheight;
+ gint par_n = 0, par_d = 0;
+
+ if ((entry->par_n != 1 || entry->par_d != 1) &&
+ (entry->par_n != entry->par_d)) {
+ par_n = entry->par_n;
+ par_d = entry->par_d;
+ }
+
+ dwidth = entry->width;
+ dheight = entry->height;
+ /* ISO file spec says track header w/h indicates track's visual presentation
+ * (so this together with pixels w/h implicitly defines PAR) */
+ if (par_n && (context->flavor != ATOMS_TREE_FLAVOR_MOV)) {
+ if (par_n > par_d) {
+ dwidth = entry->width * par_n / par_d;
+ dheight = entry->height;
+ } else {
+ dwidth = entry->width * par_n / par_d;
+ dheight = entry->height;
+ }
+ }
- atom_trak_set_video_commons (trak, context, scale, entry->width,
- entry->height);
+ atom_trak_set_video_commons (trak, context, scale, dwidth, dheight);
ste = atom_trak_add_video_entry (trak, context, entry->fourcc);
trak->is_video = TRUE;
@@ -2825,6 +2961,12 @@ atom_trak_set_video_type (AtomTRAK * trak, AtomsContext * context,
if (ext)
ste->extension_atoms = g_list_prepend (ste->extension_atoms, ext);
+
+ /* QT spec has a pasp extension atom in stsd that can hold PAR */
+ if (par_n && (context->flavor == ATOMS_TREE_FLAVOR_MOV)) {
+ ste->extension_atoms = g_list_append (ste->extension_atoms,
+ build_pasp_extension (trak, par_n, par_d));
+ }
}
/* some sample description construction helpers */
diff --git a/gst/qtmux/atoms.h b/gst/qtmux/atoms.h
index 23bc19bb..4c94141b 100644
--- a/gst/qtmux/atoms.h
+++ b/gst/qtmux/atoms.h
@@ -55,7 +55,8 @@
typedef enum _AtomsTreeFlavor
{
ATOMS_TREE_FLAVOR_MOV,
- ATOMS_TREE_FLAVOR_ISOM
+ ATOMS_TREE_FLAVOR_ISOM,
+ ATOMS_TREE_FLAVOR_3GP
} AtomsTreeFlavor;
typedef struct _AtomsContext
@@ -509,6 +510,9 @@ typedef struct _AtomUDTA
{
Atom header;
+ /* list of AtomInfo */
+ GList* entries;
+ /* or list is further down */
AtomMETA *meta;
} AtomUDTA;
@@ -526,6 +530,9 @@ typedef struct _AtomTRAK
typedef struct _AtomMOOV
{
+ /* style */
+ AtomsContext context;
+
Atom header;
AtomMVHD mvhd;
@@ -602,6 +609,8 @@ typedef struct
guint depth;
guint frame_count;
gint color_table_id;
+ guint par_n;
+ guint par_d;
GstBuffer *codec_data;
} VisualSampleEntry;
@@ -649,4 +658,14 @@ void atom_moov_add_tag (AtomMOOV *moov, guint32 fourcc, guint32 flags,
const guint8 * data, guint size);
void atom_moov_add_blob_tag (AtomMOOV *moov, guint8 *data, guint size);
+void atom_moov_add_3gp_str_tag (AtomMOOV * moov, guint32 fourcc, const gchar * value);
+void atom_moov_add_3gp_uint_tag (AtomMOOV * moov, guint32 fourcc, guint16 value);
+void atom_moov_add_3gp_str_int_tag (AtomMOOV * moov, guint32 fourcc, const gchar * value,
+ gint16 ivalue);
+void atom_moov_add_3gp_tag (AtomMOOV * moov, guint32 fourcc, guint8 * data,
+ guint size);
+
+#define GST_QT_MUX_DEFAULT_TAG_LANGUAGE "eng"
+guint16 language_code (const char * lang);
+
#endif /* __ATOMS_H__ */
diff --git a/gst/qtmux/fourcc.h b/gst/qtmux/fourcc.h
index 3db60036..9b1fe65a 100644
--- a/gst/qtmux/fourcc.h
+++ b/gst/qtmux/fourcc.h
@@ -104,6 +104,7 @@ G_BEGIN_DECLS
#define FOURCC_wave GST_MAKE_FOURCC('w','a','v','e')
#define FOURCC_appl GST_MAKE_FOURCC('a','p','p','l')
#define FOURCC_esds GST_MAKE_FOURCC('e','s','d','s')
+#define FOURCC_pasp GST_MAKE_FOURCC('p','a','s','p')
#define FOURCC_hnti GST_MAKE_FOURCC('h','n','t','i')
#define FOURCC_rtp_ GST_MAKE_FOURCC('r','t','p',' ')
#define FOURCC_sdp_ GST_MAKE_FOURCC('s','d','p',' ')
@@ -181,6 +182,15 @@ G_BEGIN_DECLS
#define FOURCC_titl GST_MAKE_FOURCC('t','i','t','l')
#define FOURCC__cmt GST_MAKE_FOURCC(0xa9, 'c','m','t')
+/* 3gp tags */
+#define FOURCC_dscp GST_MAKE_FOURCC('d','s','c','p')
+#define FOURCC_perf GST_MAKE_FOURCC('p','e','r','f')
+#define FOURCC_auth GST_MAKE_FOURCC('a','u','t','h')
+#define FOURCC_yrrc GST_MAKE_FOURCC('y','r','r','c')
+#define FOURCC_albm GST_MAKE_FOURCC('a','l','b','m')
+#define FOURCC_loci GST_MAKE_FOURCC('l','o','c','i')
+#define FOURCC_kywd GST_MAKE_FOURCC('k','y','w','d')
+
G_END_DECLS
#endif /* __FOURCC_H__ */
diff --git a/gst/qtmux/gstqtmux.c b/gst/qtmux/gstqtmux.c
index 03b0a1a6..8d811f41 100644
--- a/gst/qtmux/gstqtmux.c
+++ b/gst/qtmux/gstqtmux.c
@@ -111,9 +111,11 @@ enum
PROP_FAST_START_TEMP_FILE
};
-#define MDAT_ATOM_HEADER_SIZE 16
+/* some spare for header size as well */
+#define MDAT_LARGE_FILE_LIMIT ((guint64) 1024 * 1024 * 1024 * 2)
+
#define DEFAULT_LARGE_FILE FALSE
-#define DEFAULT_MOVIE_TIMESCALE 600
+#define DEFAULT_MOVIE_TIMESCALE 1000
#define DEFAULT_DO_CTTS FALSE
#define DEFAULT_FAST_START FALSE
#define DEFAULT_FAST_START_TEMP_FILE NULL
@@ -349,8 +351,294 @@ gst_qt_mux_finalize (GObject * object)
G_OBJECT_CLASS (parent_class)->finalize (object);
}
-/* FIXME approach below is pretty Apple/MOV/MP4/iTunes specific,
- * and as such does not comply with e.g. 3GPP specs */
+static void
+gst_qt_mux_add_mp4_tag (GstQTMux * qtmux, const GstTagList * list,
+ const char *tag, const char *tag2, guint32 fourcc)
+{
+ switch (gst_tag_get_type (tag)) {
+ /* strings */
+ case G_TYPE_STRING:
+ {
+ gchar *str = NULL;
+
+ if (!gst_tag_list_get_string (list, tag, &str) || !str)
+ break;
+ GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
+ GST_FOURCC_ARGS (fourcc), str);
+ atom_moov_add_str_tag (qtmux->moov, fourcc, str);
+ g_free (str);
+ break;
+ }
+ /* double */
+ case G_TYPE_DOUBLE:
+ {
+ gdouble value;
+
+ if (!gst_tag_list_get_double (list, tag, &value))
+ break;
+ GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u",
+ GST_FOURCC_ARGS (fourcc), (gint) value);
+ atom_moov_add_uint_tag (qtmux->moov, fourcc, 21, (gint) value);
+ break;
+ }
+ /* paired unsigned integers */
+ case G_TYPE_UINT:
+ {
+ guint value;
+ guint count;
+
+ if (!gst_tag_list_get_uint (list, tag, &value) ||
+ !gst_tag_list_get_uint (list, tag2, &count))
+ break;
+ GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u/%u",
+ GST_FOURCC_ARGS (fourcc), value, count);
+ atom_moov_add_uint_tag (qtmux->moov, fourcc, 0,
+ value << 16 | (count & 0xFFFF));
+ break;
+ }
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static void
+gst_qt_mux_add_mp4_date (GstQTMux * qtmux, const GstTagList * list,
+ const char *tag, const char *tag2, guint32 fourcc)
+{
+ GDate *date = NULL;
+ GDateYear year;
+ GDateMonth month;
+ GDateDay day;
+ gchar *str;
+
+ g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_DATE);
+
+ if (!gst_tag_list_get_date (list, tag, &date) || !date)
+ return;
+
+ year = g_date_get_year (date);
+ month = g_date_get_month (date);
+ day = g_date_get_day (date);
+
+ if (year == G_DATE_BAD_YEAR && month == G_DATE_BAD_MONTH &&
+ day == G_DATE_BAD_DAY) {
+ GST_WARNING_OBJECT (qtmux, "invalid date in tag");
+ return;
+ }
+
+ str = g_strdup_printf ("%u-%u-%u", year, month, day);
+ GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
+ GST_FOURCC_ARGS (fourcc), str);
+ atom_moov_add_str_tag (qtmux->moov, fourcc, str);
+}
+
+static void
+gst_qt_mux_add_mp4_cover (GstQTMux * qtmux, const GstTagList * list,
+ const char *tag, const char *tag2, guint32 fourcc)
+{
+ GValue value = { 0, };
+ GstBuffer *buf;
+ GstCaps *caps;
+ GstStructure *structure;
+ gint flags = 0;
+
+ g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_BUFFER);
+
+ if (!gst_tag_list_copy_value (&value, list, tag))
+ return;
+
+ buf = gst_value_get_buffer (&value);
+ if (!buf)
+ goto done;
+
+ caps = gst_buffer_get_caps (buf);
+ if (!caps) {
+ GST_WARNING_OBJECT (qtmux, "preview image without caps");
+ goto done;
+ }
+
+ GST_DEBUG_OBJECT (qtmux, "preview image caps %" GST_PTR_FORMAT, caps);
+
+ structure = gst_caps_get_structure (caps, 0);
+ if (gst_structure_has_name (structure, "image/jpeg"))
+ flags = 13;
+ else if (gst_structure_has_name (structure, "image/png"))
+ flags = 14;
+ gst_caps_unref (caps);
+
+ if (!flags) {
+ GST_WARNING_OBJECT (qtmux, "preview image format not supported");
+ goto done;
+ }
+
+ GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT
+ " -> image size %d", GST_FOURCC_ARGS (fourcc), GST_BUFFER_SIZE (buf));
+ atom_moov_add_tag (qtmux->moov, fourcc, flags, GST_BUFFER_DATA (buf),
+ GST_BUFFER_SIZE (buf));
+done:
+ g_value_unset (&value);
+}
+
+static void
+gst_qt_mux_add_3gp_str (GstQTMux * qtmux, const GstTagList * list,
+ const char *tag, const char *tag2, guint32 fourcc)
+{
+ gchar *str = NULL;
+ guint number;
+
+ g_return_if_fail (gst_tag_get_type (tag) == G_TYPE_STRING);
+ g_return_if_fail (!tag2 || gst_tag_get_type (tag2) == G_TYPE_UINT);
+
+ if (!gst_tag_list_get_string (list, tag, &str) || !str)
+ return;
+
+ if (tag2)
+ if (!gst_tag_list_get_uint (list, tag2, &number))
+ tag2 = NULL;
+
+ if (!tag2) {
+ GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
+ GST_FOURCC_ARGS (fourcc), str);
+ atom_moov_add_3gp_str_tag (qtmux->moov, fourcc, str);
+ } else {
+ GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s/%d",
+ GST_FOURCC_ARGS (fourcc), str, number);
+ atom_moov_add_3gp_str_int_tag (qtmux->moov, fourcc, str, number);
+ }
+
+ g_free (str);
+}
+
+static void
+gst_qt_mux_add_3gp_date (GstQTMux * qtmux, const GstTagList * list,
+ const char *tag, const char *tag2, guint32 fourcc)
+{
+ GDate *date = NULL;
+ GDateYear year;
+
+ g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_DATE);
+
+ if (!gst_tag_list_get_date (list, tag, &date) || !date)
+ return;
+
+ year = g_date_get_year (date);
+
+ if (year == G_DATE_BAD_YEAR) {
+ GST_WARNING_OBJECT (qtmux, "invalid date in tag");
+ return;
+ }
+
+ GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %d", year);
+ atom_moov_add_3gp_uint_tag (qtmux->moov, fourcc, year);
+}
+
+static void
+gst_qt_mux_add_3gp_location (GstQTMux * qtmux, const GstTagList * list,
+ const char *tag, const char *tag2, guint32 fourcc)
+{
+ gdouble latitude = -360, longitude = -360, altitude = 0;
+ gchar *location = NULL;
+ guint8 *data, *ddata;
+ gint size = 0, len = 0;
+ gboolean ret = FALSE;
+
+ g_return_if_fail (strcmp (tag, GST_TAG_GEO_LOCATION_NAME) == 0);
+
+ ret = gst_tag_list_get_string (list, tag, &location);
+ ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_LONGITUDE,
+ &longitude);
+ ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_LATITUDE,
+ &latitude);
+ ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_ELEVATION,
+ &altitude);
+
+ if (!ret)
+ return;
+
+ if (location)
+ len = strlen (location);
+ size += len + 1 + 2;
+
+ /* role + (long, lat, alt) + body + notes */
+ size += 1 + 3 * 4 + 1 + 1;
+
+ data = ddata = g_malloc (size);
+
+ /* language tag */
+ GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE));
+ /* location */
+ if (location)
+ memcpy (data + 2, location, len);
+ GST_WRITE_UINT8 (data + 2 + len, 0);
+ data += len + 1 + 2;
+ /* role */
+ GST_WRITE_UINT8 (data, 0);
+ /* long, lat, alt */
+ GST_WRITE_UINT32_BE (data + 1, (guint32) (longitude * 65536.0));
+ GST_WRITE_UINT32_BE (data + 5, (guint32) (latitude * 65536.0));
+ GST_WRITE_UINT32_BE (data + 9, (guint32) (altitude * 65536.0));
+ /* neither astronomical body nor notes */
+ GST_WRITE_UINT16_BE (data + 13, 0);
+
+ GST_DEBUG_OBJECT (qtmux, "Adding tag 'loci'");
+ atom_moov_add_3gp_tag (qtmux->moov, fourcc, ddata, size);
+ g_free (ddata);
+}
+
+static void
+gst_qt_mux_add_3gp_keywords (GstQTMux * qtmux, const GstTagList * list,
+ const char *tag, const char *tag2, guint32 fourcc)
+{
+ gchar *keywords = NULL;
+ guint8 *data, *ddata;
+ gint size = 0, i;
+ gchar **kwds;
+
+ g_return_if_fail (strcmp (tag, GST_TAG_KEYWORDS) == 0);
+
+ if (!gst_tag_list_get_string (list, tag, &keywords) || !keywords)
+ return;
+
+ kwds = g_strsplit (keywords, ",", 0);
+
+ size = 0;
+ for (i = 0; kwds[i]; i++) {
+ /* size byte + null-terminator */
+ size += strlen (kwds[i]) + 1 + 1;
+ }
+
+ /* language tag + count + keywords */
+ size += 2 + 1;
+
+ data = ddata = g_malloc (size);
+
+ /* language tag */
+ GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE));
+ /* count */
+ GST_WRITE_UINT8 (data + 2, i);
+ data += 3;
+ /* keywords */
+ for (i = 0; kwds[i]; ++i) {
+ gint len = strlen (kwds[i]);
+
+ GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
+ GST_FOURCC_ARGS (fourcc), kwds[i]);
+ /* size */
+ GST_WRITE_UINT8 (data, len + 1);
+ memcpy (data + 1, kwds[i], len + 1);
+ data += len + 2;
+ }
+
+ g_strfreev (kwds);
+
+ atom_moov_add_3gp_tag (qtmux->moov, fourcc, ddata, size);
+ g_free (ddata);
+}
+
+
+typedef void (*GstQTMuxAddTagFunc) (GstQTMux * mux, const GstTagList * list,
+ const char *tag, const char *tag2, guint32 fourcc);
/*
* Struct to record mappings from gstreamer tags to fourcc codes
@@ -360,25 +648,42 @@ typedef struct _GstTagToFourcc
guint32 fourcc;
const gchar *gsttag;
const gchar *gsttag2;
+ const GstQTMuxAddTagFunc func;
} GstTagToFourcc;
/* tag list tags to fourcc matching */
-static const GstTagToFourcc tag_matches[] = {
- {FOURCC__alb, GST_TAG_ALBUM,},
- {FOURCC__ART, GST_TAG_ARTIST,},
- {FOURCC__cmt, GST_TAG_COMMENT,},
- {FOURCC__wrt, GST_TAG_COMPOSER,},
- {FOURCC__gen, GST_TAG_GENRE,},
- {FOURCC__nam, GST_TAG_TITLE,},
- {FOURCC__des, GST_TAG_DESCRIPTION,},
- {FOURCC__too, GST_TAG_ENCODER,},
- {FOURCC_cprt, GST_TAG_COPYRIGHT,},
- {FOURCC_keyw, GST_TAG_KEYWORDS,},
- {FOURCC__day, GST_TAG_DATE,},
- {FOURCC_tmpo, GST_TAG_BEATS_PER_MINUTE,},
- {FOURCC_trkn, GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT},
- {FOURCC_disk, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT},
- {FOURCC_covr, GST_TAG_PREVIEW_IMAGE,},
+static const GstTagToFourcc tag_matches_mp4[] = {
+ {FOURCC__alb, GST_TAG_ALBUM, NULL, gst_qt_mux_add_mp4_tag},
+ {FOURCC__ART, GST_TAG_ARTIST, NULL, gst_qt_mux_add_mp4_tag},
+ {FOURCC__cmt, GST_TAG_COMMENT, NULL, gst_qt_mux_add_mp4_tag},
+ {FOURCC__wrt, GST_TAG_COMPOSER, NULL, gst_qt_mux_add_mp4_tag},
+ {FOURCC__gen, GST_TAG_GENRE, NULL, gst_qt_mux_add_mp4_tag},
+ {FOURCC__nam, GST_TAG_TITLE, NULL, gst_qt_mux_add_mp4_tag},
+ {FOURCC__des, GST_TAG_DESCRIPTION, NULL, gst_qt_mux_add_mp4_tag},
+ {FOURCC__too, GST_TAG_ENCODER, NULL, gst_qt_mux_add_mp4_tag},
+ {FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, gst_qt_mux_add_mp4_tag},
+ {FOURCC_keyw, GST_TAG_KEYWORDS, NULL, gst_qt_mux_add_mp4_tag},
+ {FOURCC__day, GST_TAG_DATE, NULL, gst_qt_mux_add_mp4_date},
+ {FOURCC_tmpo, GST_TAG_BEATS_PER_MINUTE, NULL, gst_qt_mux_add_mp4_tag},
+ {FOURCC_trkn, GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT,
+ gst_qt_mux_add_mp4_tag},
+ {FOURCC_disk, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT,
+ gst_qt_mux_add_mp4_tag},
+ {FOURCC_covr, GST_TAG_PREVIEW_IMAGE, NULL, gst_qt_mux_add_mp4_cover},
+ {0, NULL,}
+};
+
+static const GstTagToFourcc tag_matches_3gp[] = {
+ {FOURCC_titl, GST_TAG_TITLE, NULL, gst_qt_mux_add_3gp_str},
+ {FOURCC_dscp, GST_TAG_DESCRIPTION, NULL, gst_qt_mux_add_3gp_str},
+ {FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, gst_qt_mux_add_3gp_str},
+ {FOURCC_perf, GST_TAG_ARTIST, NULL, gst_qt_mux_add_3gp_str},
+ {FOURCC_auth, GST_TAG_COMPOSER, NULL, gst_qt_mux_add_3gp_str},
+ {FOURCC_gnre, GST_TAG_GENRE, NULL, gst_qt_mux_add_3gp_str},
+ {FOURCC_kywd, GST_TAG_KEYWORDS, NULL, gst_qt_mux_add_3gp_keywords},
+ {FOURCC_yrrc, GST_TAG_DATE, NULL, gst_qt_mux_add_3gp_date},
+ {FOURCC_albm, GST_TAG_ALBUM, GST_TAG_TRACK_NUMBER, gst_qt_mux_add_3gp_str},
+ {FOURCC_loci, GST_TAG_GEO_LOCATION_NAME, NULL, gst_qt_mux_add_3gp_location},
{0, NULL,}
};
@@ -388,127 +693,35 @@ static const GstTagToFourcc tag_matches[] = {
static void
gst_qt_mux_add_metadata_tags (GstQTMux * qtmux, const GstTagList * list)
{
+ GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
guint32 fourcc;
gint i;
const gchar *tag, *tag2;
+ const GstTagToFourcc *tag_matches;
+
+ switch (qtmux_klass->format) {
+ case GST_QT_MUX_FORMAT_3GP:
+ tag_matches = tag_matches_3gp;
+ break;
+ case GST_QT_MUX_FORMAT_MJ2:
+ tag_matches = NULL;
+ break;
+ default:
+ /* sort of iTunes style for mp4 and QT (?) */
+ tag_matches = tag_matches_mp4;
+ break;
+ }
+
+ if (!tag_matches)
+ return;
for (i = 0; tag_matches[i].fourcc; i++) {
fourcc = tag_matches[i].fourcc;
tag = tag_matches[i].gsttag;
tag2 = tag_matches[i].gsttag2;
- switch (gst_tag_get_type (tag)) {
- /* strings */
- case G_TYPE_STRING:
- {
- gchar *str = NULL;
-
- if (!gst_tag_list_get_string (list, tag, &str) || !str)
- break;
- GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
- GST_FOURCC_ARGS (fourcc), str);
- atom_moov_add_str_tag (qtmux->moov, fourcc, str);
- g_free (str);
- break;
- }
- /* double */
- case G_TYPE_DOUBLE:
- {
- gdouble value;
-
- if (!gst_tag_list_get_double (list, tag, &value))
- break;
- GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u",
- GST_FOURCC_ARGS (fourcc), (gint) value);
- atom_moov_add_uint_tag (qtmux->moov, fourcc, 21, (gint) value);
- break;
- }
- /* paired unsigned integers */
- case G_TYPE_UINT:
- {
- guint value;
- guint count;
-
- if (!gst_tag_list_get_uint (list, tag, &value) ||
- !gst_tag_list_get_uint (list, tag2, &count))
- break;
- GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u/%u",
- GST_FOURCC_ARGS (fourcc), value, count);
- atom_moov_add_uint_tag (qtmux->moov, fourcc, 0,
- value << 16 | (count & 0xFFFF));
- break;
- }
- default:
- {
- if (gst_tag_get_type (tag) == GST_TYPE_DATE) {
- GDate *date = NULL;
- GDateYear year;
- GDateMonth month;
- GDateDay day;
- gchar *str;
-
- if (!gst_tag_list_get_date (list, tag, &date) || !date)
- break;
- year = g_date_get_year (date);
- month = g_date_get_month (date);
- day = g_date_get_day (date);
-
- if (year == G_DATE_BAD_YEAR && month == G_DATE_BAD_MONTH &&
- day == G_DATE_BAD_DAY) {
- GST_WARNING_OBJECT (qtmux, "invalid date in tag");
- break;
- }
-
- str = g_strdup_printf ("%u-%u-%u", year, month, day);
- GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
- GST_FOURCC_ARGS (fourcc), str);
- atom_moov_add_str_tag (qtmux->moov, fourcc, str);
- } else if (gst_tag_get_type (tag) == GST_TYPE_BUFFER) {
- GValue value = { 0, };
- GstBuffer *buf;
- GstCaps *caps;
- GstStructure *structure;
- gint flags = 0;
-
- if (!gst_tag_list_copy_value (&value, list, tag))
- break;
-
- buf = gst_value_get_buffer (&value);
- if (!buf)
- goto done;
-
- caps = gst_buffer_get_caps (buf);
- if (!caps) {
- GST_WARNING_OBJECT (qtmux, "preview image without caps");
- goto done;
- }
-
- GST_DEBUG_OBJECT (qtmux, "preview image caps %" GST_PTR_FORMAT, caps);
-
- structure = gst_caps_get_structure (caps, 0);
- if (gst_structure_has_name (structure, "image/jpeg"))
- flags = 13;
- else if (gst_structure_has_name (structure, "image/png"))
- flags = 14;
- gst_caps_unref (caps);
-
- if (!flags) {
- GST_WARNING_OBJECT (qtmux, "preview image format not supported");
- goto done;
- }
-
- GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT
- " -> image size %d", GST_FOURCC_ARGS (fourcc),
- GST_BUFFER_SIZE (buf));
- atom_moov_add_tag (qtmux->moov, fourcc, flags, GST_BUFFER_DATA (buf),
- GST_BUFFER_SIZE (buf));
- done:
- g_value_unset (&value);
- } else
- g_assert_not_reached ();
- break;
- }
- }
+ g_assert (tag_matches[i].func);
+ tag_matches[i].func (qtmux, list, tag, tag2, fourcc);
}
/* add unparsed blobs if present */
@@ -532,8 +745,12 @@ gst_qt_mux_add_metadata_tags (GstQTMux * qtmux, const GstTagList * list)
GST_PTR_FORMAT, i, num_tags, GST_BUFFER_SIZE (buf), caps);
s = gst_caps_get_structure (caps, 0);
if (s && (style = gst_structure_get_string (s, "style"))) {
- /* FIXME make into a parameter */
- if (strcmp (style, "itunes") == 0) {
+ /* try to prevent some style tag ending up into another variant
+ * (todo: make into a list if more cases) */
+ if ((strcmp (style, "itunes") == 0 &&
+ qtmux_klass->format == GST_QT_MUX_FORMAT_MP4) ||
+ (strcmp (style, "iso") == 0 &&
+ qtmux_klass->format == GST_QT_MUX_FORMAT_3GP)) {
GST_DEBUG_OBJECT (qtmux, "Adding private tag");
atom_moov_add_blob_tag (qtmux->moov, GST_BUFFER_DATA (buf),
GST_BUFFER_SIZE (buf));
@@ -600,7 +817,7 @@ gst_qt_mux_send_buffer (GstQTMux * qtmux, GstBuffer * buf, guint64 * offset,
res = gst_pad_push (qtmux->srcpad, buf);
}
- if (offset)
+ if (G_LIKELY (offset))
*offset += size;
return res;
@@ -690,7 +907,8 @@ seek_failed:
* seek back to it later and update when the streams have finished.
*/
static GstFlowReturn
-gst_qt_mux_send_mdat_header (GstQTMux * qtmux, guint64 * off, guint64 size)
+gst_qt_mux_send_mdat_header (GstQTMux * qtmux, guint64 * off, guint64 size,
+ gboolean extended)
{
Atom *node_header;
GstBuffer *buf;
@@ -702,11 +920,15 @@ gst_qt_mux_send_mdat_header (GstQTMux * qtmux, guint64 * off, guint64 size)
node_header = g_malloc0 (sizeof (Atom));
node_header->type = FOURCC_mdat;
- /* use extended size */
- node_header->size = 1;
- node_header->extended_size = 0;
- if (size)
- node_header->extended_size = size;
+ if (extended) {
+ /* use extended size */
+ node_header->size = 1;
+ node_header->extended_size = 0;
+ if (size)
+ node_header->extended_size = size + 16;
+ } else {
+ node_header->size = size + 8;
+ }
size = offset = 0;
if (atom_copy_data (node_header, &data, &size, &offset) == 0)
@@ -740,14 +962,31 @@ gst_qt_mux_update_mdat_size (GstQTMux * qtmux, guint64 mdat_pos,
{
GstEvent *event;
GstBuffer *buf;
+ gboolean large_file;
+
+ large_file = (mdat_size > MDAT_LARGE_FILE_LIMIT);
+
+ if (large_file)
+ mdat_pos += 8;
/* seek and rewrite the header */
event = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES,
mdat_pos, GST_CLOCK_TIME_NONE, 0);
gst_pad_push_event (qtmux->srcpad, event);
- buf = gst_buffer_new_and_alloc (sizeof (guint64));
- GST_WRITE_UINT64_BE (GST_BUFFER_DATA (buf), mdat_size);
+ if (large_file) {
+ buf = gst_buffer_new_and_alloc (sizeof (guint64));
+ GST_WRITE_UINT64_BE (GST_BUFFER_DATA (buf), mdat_size + 16);
+ } else {
+ guint8 *data;
+
+ buf = gst_buffer_new_and_alloc (16);
+ data = GST_BUFFER_DATA (buf);
+ GST_WRITE_UINT32_BE (data, 8);
+ GST_WRITE_UINT32_LE (data + 4, FOURCC_free);
+ GST_WRITE_UINT32_BE (data + 8, mdat_size + 8);
+ GST_WRITE_UINT32_LE (data + 12, FOURCC_mdat);
+ }
return gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
}
@@ -798,6 +1037,7 @@ gst_qt_mux_stop_file (GstQTMux * qtmux)
/* tags into file metadata */
gst_qt_mux_setup_metadata (qtmux);
+ large_file = (qtmux->mdat_size > MDAT_LARGE_FILE_LIMIT);
/* if faststart, update the offset of the atoms in the movie with the offset
* that the movie headers before mdat will cause */
if (qtmux->fast_start_file) {
@@ -807,7 +1047,7 @@ gst_qt_mux_stop_file (GstQTMux * qtmux)
goto serialize_error;
GST_DEBUG_OBJECT (qtmux, "calculated moov atom size %" G_GUINT64_FORMAT,
size);
- offset += qtmux->header_size + MDAT_ATOM_HEADER_SIZE;
+ offset += qtmux->header_size + (large_file ? 16 : 8);
} else
offset = qtmux->header_size;
atom_moov_chunks_add_offset (qtmux->moov, offset);
@@ -828,12 +1068,11 @@ gst_qt_mux_stop_file (GstQTMux * qtmux)
GST_DEBUG_OBJECT (qtmux, "Pushing movie atoms");
gst_qt_mux_send_buffer (qtmux, buffer, NULL, FALSE);
- /* total mdat size as of now also includes the atom header */
- qtmux->mdat_size += MDAT_ATOM_HEADER_SIZE;
/* if needed, send mdat atom and move buffered data into it */
if (qtmux->fast_start_file) {
/* mdat size = accumulated (buffered data) + mdat atom header */
- ret = gst_qt_mux_send_mdat_header (qtmux, NULL, qtmux->mdat_size);
+ ret = gst_qt_mux_send_mdat_header (qtmux, NULL, qtmux->mdat_size,
+ large_file);
if (ret != GST_FLOW_OK)
return ret;
ret = gst_qt_mux_send_buffered_data (qtmux, NULL);
@@ -927,9 +1166,9 @@ gst_qt_mux_start_file (GstQTMux * qtmux)
if (!qtmux->fast_start_file)
goto open_failed;
} else {
- ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0);
- /* mdat size position = current header pos - extended header size */
- qtmux->mdat_pos = qtmux->header_size - sizeof (guint64);
+ /* extended to ensure some spare space */
+ qtmux->mdat_pos = qtmux->header_size;
+ ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0, TRUE);
}
GST_OBJECT_UNLOCK (qtmux);
@@ -1464,7 +1703,6 @@ gst_qt_mux_video_sink_set_caps (GstPad * pad, GstCaps * caps)
par_den = 1;
gst_structure_get_fraction (structure, "pixel-aspect-ratio", &par_num,
&par_den);
- /* FIXME: pixel-aspect-ratio */
qtpad->is_out_of_order = FALSE;
@@ -1477,6 +1715,8 @@ gst_qt_mux_video_sink_set_caps (GstPad * pad, GstCaps * caps)
/* set common properties */
entry.width = width;
entry.height = height;
+ entry.par_n = par_num;
+ entry.par_d = par_den;
/* should be OK according to qt and iso spec, override if really needed */
entry.color_table_id = -1;
entry.frame_count = 1;
diff --git a/gst/qtmux/gstqtmux.h b/gst/qtmux/gstqtmux.h
index a4701cc1..567773a0 100644
--- a/gst/qtmux/gstqtmux.h
+++ b/gst/qtmux/gstqtmux.h
@@ -109,7 +109,7 @@ struct _GstQTMux
guint64 header_size;
/* accumulated size of raw media data (a priori not including mdat header) */
guint64 mdat_size;
- /* position of mdat extended size field (for later updating) */
+ /* position of mdat atom (for later updating) */
guint64 mdat_pos;
/* atom helper objects */
diff --git a/gst/qtmux/gstqtmuxmap.c b/gst/qtmux/gstqtmuxmap.c
index e0e4faed..808856a2 100644
--- a/gst/qtmux/gstqtmuxmap.c
+++ b/gst/qtmux/gstqtmuxmap.c
@@ -201,6 +201,8 @@ gst_qt_mux_map_format_to_flavor (GstQTMuxFormat format)
{
if (format == GST_QT_MUX_FORMAT_QT)
return ATOMS_TREE_FLAVOR_MOV;
+ else if (format == GST_QT_MUX_FORMAT_3GP)
+ return ATOMS_TREE_FLAVOR_3GP;
else
return ATOMS_TREE_FLAVOR_ISOM;
}