diff options
author | Dave Robillard <dave@drobilla.net> | 2009-06-19 21:03:10 -0400 |
---|---|---|
committer | Dave Robillard <dave@drobilla.net> | 2009-06-19 21:03:10 -0400 |
commit | 23953f27c870c42ce369d717bc15b7f8001691a1 (patch) | |
tree | 19b3999c7b0c36fcefcbbcaaaa9102f612670ca7 /gst/qtmux | |
parent | d365eafd8f2cdb1ded93fe4bd95e568026abf0da (diff) | |
parent | 925e83ee60c5406b2e5f0f39b0da0f90370efc27 (diff) | |
download | gst-plugins-bad-23953f27c870c42ce369d717bc15b7f8001691a1.tar.gz gst-plugins-bad-23953f27c870c42ce369d717bc15b7f8001691a1.tar.bz2 gst-plugins-bad-23953f27c870c42ce369d717bc15b7f8001691a1.zip |
Merge branch 'fdo' into lv2
Diffstat (limited to 'gst/qtmux')
-rw-r--r-- | gst/qtmux/atoms.c | 186 | ||||
-rw-r--r-- | gst/qtmux/atoms.h | 21 | ||||
-rw-r--r-- | gst/qtmux/fourcc.h | 10 | ||||
-rw-r--r-- | gst/qtmux/gstqtmux.c | 542 | ||||
-rw-r--r-- | gst/qtmux/gstqtmux.h | 2 | ||||
-rw-r--r-- | gst/qtmux/gstqtmuxmap.c | 2 |
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; } |