From: Thiago Sousa Santos Date: Sat, 8 Nov 2008 02:00:58 +0000 (+0000) Subject: Copy qtmux from revision 148 of the gst-qtmux repository. X-Git-Tag: RELEASE-0.10.29~231 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=c991a04a93ff386a917b40c5352b6813e2f3e527;p=platform%2Fupstream%2Fgst-plugins-good.git Copy qtmux from revision 148 of the gst-qtmux repository. Original commit message from CVS: patch by: Thiago Sousa Santos * configure.ac: * gst/quicktime/Makefile.am: * gst/quicktime/atoms.c: * gst/quicktime/atoms.h: * gst/quicktime/descriptors.c: * gst/quicktime/descriptors.h: * gst/quicktime/fourcc.h: * gst/quicktime/ftypcc.h: * gst/quicktime/gstqtmux.c: * gst/quicktime/gstqtmux.h: * gst/quicktime/gstqtmuxmap.c: * gst/quicktime/gstqtmuxmap.h: * gst/quicktime/properties.c: * gst/quicktime/properties.h: Copy qtmux from revision 148 of the gst-qtmux repository. Fixes #550280. --- diff --git a/gst/quicktime/atoms.c b/gst/quicktime/atoms.c new file mode 100644 index 000000000..3b0d83fe6 --- /dev/null +++ b/gst/quicktime/atoms.c @@ -0,0 +1,2936 @@ +/* Quicktime muxer plugin for GStreamer + * Copyright (C) 2008 Thiago Sousa Santos + * Copyright (C) 2008 Mark Nauwelaerts + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "atoms.h" +#include +#include + +/* only needed for gst_util_uint64_scale */ +#include + +/** + * Creates a new AtomsContext for the given flavor. + */ +AtomsContext * +atoms_context_new (AtomsTreeFlavor flavor) +{ + AtomsContext *context = g_new0 (AtomsContext, 1); + context->flavor = flavor; + return context; +} + +/** + * Frees an AtomsContext and all memory associated with it + */ +void +atoms_context_free (AtomsContext * context) +{ + g_free (context); +} + +/* -- creation, initialization, clear and free functions -- */ + +#define SECS_PER_DAY (24 * 60 * 60) +#define LEAP_YEARS_FROM_1904_TO_1970 17 + +static guint64 +get_current_qt_time () +{ + GTimeVal timeval; + + g_get_current_time (&timeval); + /* FIXME this should use UTC coordinated time */ + return timeval.tv_sec + (((1970 - 1904) * (guint64) 365) + + LEAP_YEARS_FROM_1904_TO_1970) * SECS_PER_DAY; +} + +static void +common_time_info_init (TimeInfo * ti) +{ + ti->creation_time = ti->modification_time = get_current_qt_time (); + ti->timescale = 0; + ti->duration = 0; +} + +static void +atom_header_set (Atom * header, guint32 fourcc, gint32 size, gint64 ext_size) +{ + header->type = fourcc; + header->size = size; + header->extended_size = ext_size; +} + +static void +atom_clear (Atom * atom) +{ +} + +static void +atom_full_init (AtomFull * full, guint32 fourcc, gint32 size, gint64 ext_size, + guint8 version, guint8 flags[3]) +{ + atom_header_set (&(full->header), fourcc, size, ext_size); + full->version = version; + full->flags[0] = flags[0]; + full->flags[1] = flags[1]; + full->flags[2] = flags[2]; +} + +static void +atom_full_clear (AtomFull * full) +{ + atom_clear (&full->header); +} + +static void +atom_full_free (AtomFull * full) +{ + atom_full_clear (full); + g_free (full); +} + +static AtomInfo * +build_atom_info_wrapper (Atom * atom, gpointer copy_func, gpointer free_func) +{ + AtomInfo *info = NULL; + + if (atom) { + info = g_new0 (AtomInfo, 1); + + info->atom = atom; + info->copy_data_func = copy_func; + info->free_func = free_func; + } + + return info; +} + +static GList * +atom_info_list_prepend_atom (GList * ai, Atom * atom, + AtomCopyDataFunc copy_func, AtomFreeFunc free_func) +{ + if (atom) + return g_list_prepend (ai, + build_atom_info_wrapper (atom, copy_func, free_func)); + else + return ai; +} + +static void +atom_info_list_free (GList * ai) +{ + while (ai) { + AtomInfo *info = (AtomInfo *) ai->data; + + info->free_func (info->atom); + g_free (info); + ai = g_list_delete_link (ai, ai); + } +} + +static AtomData * +atom_data_new (guint32 fourcc) +{ + AtomData *data = g_new0 (AtomData, 1); + + atom_header_set (&data->header, fourcc, 0, 0); + return data; +} + +static void +atom_data_alloc_mem (AtomData * data, guint32 size) +{ + if (data->data) { + g_free (data->data); + } + data->data = g_new0 (guint8, size); + data->datalen = size; +} + +static AtomData * +atom_data_new_from_gst_buffer (guint32 fourcc, const GstBuffer * buf) +{ + AtomData *data = atom_data_new (fourcc); + + atom_data_alloc_mem (data, GST_BUFFER_SIZE (buf)); + g_memmove (data->data, GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf)); + return data; +} + +static void +atom_data_free (AtomData * data) +{ + atom_clear (&data->header); + g_free (data->data); + g_free (data); +} + +static void +atom_ftyp_init (AtomFTYP * ftyp, guint32 major, guint32 version, GList * brands) +{ + gint index; + GList *it = NULL; + + atom_header_set (&ftyp->header, FOURCC_ftyp, 16, 0); + ftyp->major_brand = major; + ftyp->version = version; + + /* always include major brand as compatible brand */ + ftyp->compatible_brands_size = g_list_length (brands) + 1; + ftyp->compatible_brands = g_new (guint32, ftyp->compatible_brands_size); + + ftyp->compatible_brands[0] = major; + index = 1; + for (it = brands; it != NULL; it = g_list_next (it)) { + ftyp->compatible_brands[index++] = GPOINTER_TO_UINT (it->data); + } +} + +AtomFTYP * +atom_ftyp_new (AtomsContext * context, guint32 major, guint32 version, + GList * brands) +{ + AtomFTYP *ftyp = g_new0 (AtomFTYP, 1); + + atom_ftyp_init (ftyp, major, version, brands); + return ftyp; +} + +void +atom_ftyp_free (AtomFTYP * ftyp) +{ + atom_clear (&ftyp->header); + g_free (ftyp->compatible_brands); + ftyp->compatible_brands = NULL; + g_free (ftyp); +} + +static void +atom_esds_init (AtomESDS * esds) +{ + guint8 flags[3] = { 0, 0, 0 }; + + atom_full_init (&esds->header, FOURCC_esds, 0, 0, 0, flags); + desc_es_init (&esds->es); +} + +static AtomESDS * +atom_esds_new () +{ + AtomESDS *esds = g_new0 (AtomESDS, 1); + + atom_esds_init (esds); + return esds; +} + +static void +atom_esds_free (AtomESDS * esds) +{ + atom_full_clear (&esds->header); + desc_es_descriptor_clear (&esds->es); + g_free (esds); +} + +static AtomFRMA * +atom_frma_new () +{ + AtomFRMA *frma = g_new0 (AtomFRMA, 1); + + atom_header_set (&frma->header, FOURCC_frma, 0, 0); + return frma; +} + +static void +atom_frma_free (AtomFRMA * frma) +{ + atom_clear (&frma->header); + g_free (frma); +} + +static AtomWAVE * +atom_wave_new () +{ + AtomWAVE *wave = g_new0 (AtomWAVE, 1); + + atom_header_set (&wave->header, FOURCC_wave, 0, 0); + return wave; +} + +static void +atom_wave_free (AtomWAVE * wave) +{ + atom_clear (&wave->header); + atom_info_list_free (wave->extension_atoms); + g_free (wave); +} + +static void +atom_sample_entry_init (SampleTableEntry * se, guint32 type) +{ + atom_header_set (&se->header, type, 0, 0); + + memset (se->reserved, 0, sizeof (guint8) * 6); + se->data_reference_index = 0; +} + +static void +atom_sample_entry_free (SampleTableEntry * se) +{ + atom_clear (&se->header); +} + +static void +sample_entry_mp4a_init (SampleTableEntryMP4A * mp4a) +{ + atom_sample_entry_init (&mp4a->se, FOURCC_mp4a); + + mp4a->version = 0; + mp4a->revision_level = 0; + mp4a->vendor = 0; + mp4a->channels = 2; + mp4a->sample_size = 16; + mp4a->compression_id = 0; + mp4a->packet_size = 0; + mp4a->sample_rate = 0; + /* following only used if version is 1 */ + mp4a->samples_per_packet = 0; + mp4a->bytes_per_packet = 0; + mp4a->bytes_per_frame = 0; + mp4a->bytes_per_sample = 0; + + mp4a->extension_atoms = NULL; +} + +static SampleTableEntryMP4A * +sample_entry_mp4a_new () +{ + SampleTableEntryMP4A *mp4a = g_new0 (SampleTableEntryMP4A, 1); + + sample_entry_mp4a_init (mp4a); + return mp4a; +} + +static void +sample_entry_mp4a_free (SampleTableEntryMP4A * mp4a) +{ + atom_sample_entry_free (&mp4a->se); + atom_info_list_free (mp4a->extension_atoms); + g_free (mp4a); +} + +static void +sample_entry_mp4v_init (SampleTableEntryMP4V * mp4v, AtomsContext * context) +{ + atom_sample_entry_init (&mp4v->se, FOURCC_mp4v); + + mp4v->version = 0; + mp4v->revision_level = 0; + mp4v->vendor = 0; + + mp4v->temporal_quality = 0; + mp4v->spatial_quality = 0; + + /* qt and ISO base media do not contradict, and examples agree */ + mp4v->horizontal_resolution = 0x00480000; + mp4v->vertical_resolution = 0x00480000; + + mp4v->datasize = 0; + mp4v->frame_count = 1; + + memset (mp4v->compressor, 0, sizeof (guint8) * 32); + + mp4v->depth = 0; + mp4v->color_table_id = 0; + + mp4v->extension_atoms = NULL; +} + +static void +sample_entry_mp4v_free (SampleTableEntryMP4V * mp4v) +{ + atom_sample_entry_free (&mp4v->se); + atom_info_list_free (mp4v->extension_atoms); + g_free (mp4v); +} + +static SampleTableEntryMP4V * +sample_entry_mp4v_new (AtomsContext * context) +{ + SampleTableEntryMP4V *mp4v = g_new0 (SampleTableEntryMP4V, 1); + + sample_entry_mp4v_init (mp4v, context); + return mp4v; +} + +static void +atom_stsd_init (AtomSTSD * stsd) +{ + guint8 flags[3] = { 0, 0, 0 }; + + atom_full_init (&stsd->header, FOURCC_stsd, 0, 0, 0, flags); + stsd->entries = NULL; +} + +static void +atom_stsd_clear (AtomSTSD * stsd) +{ + GList *walker; + + atom_full_clear (&stsd->header); + walker = stsd->entries; + while (walker) { + GList *aux = walker; + SampleTableEntry *se = (SampleTableEntry *) aux->data; + + walker = g_list_next (walker); + stsd->entries = g_list_remove_link (stsd->entries, aux); + + switch (se->kind) { + case AUDIO: + sample_entry_mp4a_free ((SampleTableEntryMP4A *) se); + break; + case VIDEO: + sample_entry_mp4v_free ((SampleTableEntryMP4V *) se); + break; + default: + /* best possible cleanup */ + atom_sample_entry_free (se); + } + g_list_free (aux); + } +} + +static void +atom_ctts_init (AtomCTTS * ctts) +{ + guint8 flags[3] = { 0, 0, 0 }; + + atom_full_init (&ctts->header, FOURCC_ctts, 0, 0, 0, flags); + ctts->entries = NULL; +} + +static AtomCTTS * +atom_ctts_new () +{ + AtomCTTS *ctts = g_new0 (AtomCTTS, 1); + + atom_ctts_init (ctts); + return ctts; +} + +static void +atom_ctts_free (AtomCTTS * ctts) +{ + GList *walker; + + atom_full_clear (&ctts->header); + walker = ctts->entries; + while (walker) { + GList *aux = walker; + + walker = g_list_next (walker); + ctts->entries = g_list_remove_link (ctts->entries, aux); + g_free ((CTTSEntry *) aux->data); + g_list_free (aux); + } + g_free (ctts); +} + +static void +atom_stts_init (AtomSTTS * stts) +{ + guint8 flags[3] = { 0, 0, 0 }; + + atom_full_init (&stts->header, FOURCC_stts, 0, 0, 0, flags); + stts->entries = NULL; +} + +static void +atom_stts_clear (AtomSTTS * stts) +{ + GList *walker; + + atom_full_clear (&stts->header); + walker = stts->entries; + while (walker) { + GList *aux = walker; + + walker = g_list_next (walker); + stts->entries = g_list_remove_link (stts->entries, aux); + g_free ((STTSEntry *) aux->data); + g_list_free (aux); + } + stts->n_entries = 0; +} + +static void +atom_stsz_init (AtomSTSZ * stsz) +{ + guint8 flags[3] = { 0, 0, 0 }; + + atom_full_init (&stsz->header, FOURCC_stsz, 0, 0, 0, flags); + stsz->sample_size = 0; + stsz->table_size = 0; + stsz->entries = NULL; +} + +static void +atom_stsz_clear (AtomSTSZ * stsz) +{ + atom_full_clear (&stsz->header); + g_list_free (stsz->entries); + stsz->entries = NULL; + stsz->table_size = 0; +} + +static void +atom_stsc_init (AtomSTSC * stsc) +{ + guint8 flags[3] = { 0, 0, 0 }; + + atom_full_init (&stsc->header, FOURCC_stsc, 0, 0, 0, flags); + stsc->entries = NULL; + stsc->n_entries = 0; +} + +static void +atom_stsc_clear (AtomSTSC * stsc) +{ + GList *walker; + + atom_full_clear (&stsc->header); + walker = stsc->entries; + while (walker) { + GList *aux = walker; + + walker = g_list_next (walker); + stsc->entries = g_list_remove_link (stsc->entries, aux); + g_free ((STSCEntry *) aux->data); + g_list_free (aux); + } + stsc->n_entries = 0; +} + +static void +atom_co64_init (AtomSTCO64 * co64) +{ + guint8 flags[3] = { 0, 0, 0 }; + + atom_full_init (&co64->header, FOURCC_co64, 0, 0, 0, flags); + co64->entries = NULL; + co64->n_entries = 0; +} + +static void +atom_stco64_clear (AtomSTCO64 * stco64) +{ + GList *walker; + + atom_full_clear (&stco64->header); + walker = stco64->entries; + while (walker) { + GList *aux = walker; + + walker = g_list_next (walker); + stco64->entries = g_list_remove_link (stco64->entries, aux); + g_free ((guint64 *) aux->data); + g_list_free (aux); + } + stco64->n_entries = 0; +} + +static void +atom_stss_init (AtomSTSS * stss) +{ + guint8 flags[3] = { 0, 0, 0 }; + + atom_full_init (&stss->header, FOURCC_stss, 0, 0, 0, flags); + stss->entries = NULL; + stss->n_entries = 0; +} + +static void +atom_stss_clear (AtomSTSS * stss) +{ + atom_full_clear (&stss->header); + g_list_free (stss->entries); + stss->entries = NULL; + stss->n_entries = 0; +} + +static void +atom_stbl_init (AtomSTBL * stbl) +{ + atom_header_set (&stbl->header, FOURCC_stbl, 0, 0); + + atom_stts_init (&stbl->stts); + atom_stss_init (&stbl->stss); + atom_stsd_init (&stbl->stsd); + atom_stsz_init (&stbl->stsz); + atom_stsc_init (&stbl->stsc); + stbl->ctts = NULL; + + atom_co64_init (&stbl->stco64); +} + +static void +atom_stbl_clear (AtomSTBL * stbl) +{ + atom_clear (&stbl->header); + atom_stsd_clear (&stbl->stsd); + atom_stts_clear (&stbl->stts); + atom_stss_clear (&stbl->stss); + atom_stsc_clear (&stbl->stsc); + atom_stsz_clear (&stbl->stsz); + if (stbl->ctts) { + atom_ctts_free (stbl->ctts); + } + atom_stco64_clear (&stbl->stco64); +} + +static void +atom_vmhd_init (AtomVMHD * vmhd, AtomsContext * context) +{ + guint8 flags[3] = { 0, 0, 1 }; + + atom_full_init (&vmhd->header, FOURCC_vmhd, 0, 0, 0, flags); + vmhd->graphics_mode = 0x0; + memset (vmhd->opcolor, 0, sizeof (guint16) * 3); + + if (context->flavor == ATOMS_TREE_FLAVOR_MOV) { + vmhd->graphics_mode = 0x40; + vmhd->opcolor[0] = 32768; + vmhd->opcolor[1] = 32768; + vmhd->opcolor[2] = 32768; + } +} + +static AtomVMHD * +atom_vmhd_new (AtomsContext * context) +{ + AtomVMHD *vmhd = g_new0 (AtomVMHD, 1); + + atom_vmhd_init (vmhd, context); + return vmhd; +} + +static void +atom_vmhd_free (AtomVMHD * vmhd) +{ + atom_full_clear (&vmhd->header); + g_free (vmhd); +} + +static void +atom_smhd_init (AtomSMHD * smhd) +{ + guint8 flags[3] = { 0, 0, 0 }; + + atom_full_init (&smhd->header, FOURCC_smhd, 0, 0, 0, flags); + smhd->balance = 0; + smhd->reserved = 0; +} + +static AtomSMHD * +atom_smhd_new () +{ + AtomSMHD *smhd = g_new0 (AtomSMHD, 1); + + atom_smhd_init (smhd); + return smhd; +} + +static void +atom_smhd_free (AtomSMHD * smhd) +{ + atom_full_clear (&smhd->header); + g_free (smhd); +} + +static void +atom_hmhd_free (AtomHMHD * hmhd) +{ + atom_full_clear (&hmhd->header); + g_free (hmhd); +} + +static void +atom_hdlr_init (AtomHDLR * hdlr) +{ + guint8 flags[3] = { 0, 0, 0 }; + + atom_full_init (&hdlr->header, FOURCC_hdlr, 0, 0, 0, flags); + + hdlr->component_type = 0; + hdlr->handler_type = 0; + hdlr->manufacturer = 0; + hdlr->flags = 0; + hdlr->flags_mask = 0; + hdlr->name = g_strdup (""); +} + +static AtomHDLR * +atom_hdlr_new () +{ + AtomHDLR *hdlr = g_new0 (AtomHDLR, 1); + + atom_hdlr_init (hdlr); + return hdlr; +} + +static void +atom_hdlr_clear (AtomHDLR * hdlr) +{ + atom_full_clear (&hdlr->header); + if (hdlr->name) { + g_free (hdlr->name); + hdlr->name = NULL; + } +} + +static void +atom_hdlr_free (AtomHDLR * hdlr) +{ + atom_hdlr_clear (hdlr); + g_free (hdlr); +} + +static void +atom_url_init (AtomURL * url) +{ + guint8 flags[3] = { 0, 0, 1 }; + + atom_full_init (&url->header, FOURCC_url_, 0, 0, 0, flags); + url->location = NULL; +} + +static void +atom_url_free (AtomURL * url) +{ + atom_full_clear (&url->header); + if (url->location) { + g_free (url->location); + url->location = NULL; + } + g_free (url); +} + +static AtomURL * +atom_url_new () +{ + AtomURL *url = g_new0 (AtomURL, 1); + + atom_url_init (url); + return url; +} + +static AtomFull * +atom_alis_new () +{ + guint8 flags[3] = { 0, 0, 1 }; + AtomFull *alis = g_new0 (AtomFull, 1); + + atom_full_init (alis, FOURCC_alis, 0, 0, 0, flags); + return alis; +} + +static void +atom_dref_init (AtomDREF * dref, AtomsContext * context) +{ + guint8 flags[3] = { 0, 0, 0 }; + + atom_full_init (&dref->header, FOURCC_dref, 0, 0, 0, flags); + + /* in either case, alis or url init arranges to set self-contained flag */ + if (context->flavor == ATOMS_TREE_FLAVOR_MOV) { + /* alis dref for qt */ + AtomFull *alis = atom_alis_new (); + dref->entries = g_list_append (dref->entries, alis); + } else { + /* url for iso spec, as 'alis' not specified there */ + AtomURL *url = atom_url_new (); + dref->entries = g_list_append (dref->entries, url); + } +} + +static void +atom_dref_clear (AtomDREF * dref) +{ + GList *walker; + + atom_full_clear (&dref->header); + walker = dref->entries; + while (walker) { + GList *aux = walker; + Atom *atom = (Atom *) aux->data; + + walker = g_list_next (walker); + dref->entries = g_list_remove_link (dref->entries, aux); + switch (atom->type) { + case FOURCC_alis: + atom_full_free ((AtomFull *) atom); + break; + case FOURCC_url_: + atom_url_free ((AtomURL *) atom); + break; + default: + /* we do nothing, better leak than crash */ + break; + } + g_list_free (aux); + } +} + +static void +atom_dinf_init (AtomDINF * dinf, AtomsContext * context) +{ + atom_header_set (&dinf->header, FOURCC_dinf, 0, 0); + atom_dref_init (&dinf->dref, context); +} + +static void +atom_dinf_clear (AtomDINF * dinf) +{ + atom_clear (&dinf->header); + atom_dref_clear (&dinf->dref); +} + +static void +atom_minf_init (AtomMINF * minf, AtomsContext * context) +{ + atom_header_set (&minf->header, FOURCC_minf, 0, 0); + + minf->vmhd = NULL; + minf->smhd = NULL; + minf->hmhd = NULL; + + if (context->flavor == ATOMS_TREE_FLAVOR_MOV) { + minf->hdlr = atom_hdlr_new (); + minf->hdlr->component_type = FOURCC_dhlr; + minf->hdlr->handler_type = FOURCC_alis; + } else { + minf->hdlr = NULL; + } + atom_dinf_init (&minf->dinf, context); + atom_stbl_init (&minf->stbl); +} + +static void +atom_minf_clear_handlers (AtomMINF * minf) +{ + if (minf->vmhd) { + atom_vmhd_free (minf->vmhd); + minf->vmhd = NULL; + } + if (minf->smhd) { + atom_smhd_free (minf->smhd); + minf->smhd = NULL; + } + if (minf->hmhd) { + atom_hmhd_free (minf->hmhd); + minf->hmhd = NULL; + } +} + +static void +atom_minf_clear (AtomMINF * minf) +{ + atom_clear (&minf->header); + atom_minf_clear_handlers (minf); + if (minf->hdlr) { + atom_hdlr_free (minf->hdlr); + } + atom_dinf_clear (&minf->dinf); + atom_stbl_clear (&minf->stbl); +} + +static void +atom_mdhd_init (AtomMDHD * mdhd) +{ + guint8 flags[3] = { 0, 0, 0 }; + + atom_full_init (&mdhd->header, FOURCC_mdhd, 0, 0, 0, flags); + common_time_info_init (&mdhd->time_info); + mdhd->language_code = 0; + mdhd->quality = 0; +} + +static void +atom_mdhd_clear (AtomMDHD * mdhd) +{ + atom_full_clear (&mdhd->header); +} + +static void +atom_mdia_init (AtomMDIA * mdia, AtomsContext * context) +{ + atom_header_set (&mdia->header, FOURCC_mdia, 0, 0); + + atom_mdhd_init (&mdia->mdhd); + atom_hdlr_init (&mdia->hdlr); + atom_minf_init (&mdia->minf, context); +} + +static void +atom_mdia_clear (AtomMDIA * mdia) +{ + atom_clear (&mdia->header); + atom_mdhd_clear (&mdia->mdhd); + atom_hdlr_clear (&mdia->hdlr); + atom_minf_clear (&mdia->minf); +} + +static void +atom_tkhd_init (AtomTKHD * tkhd, AtomsContext * context) +{ + /* + * flags info + * 1 -> track enabled + * 2 -> track in movie + * 4 -> track in preview + */ + guint8 flags[3] = { 0, 0, 7 }; + + atom_full_init (&tkhd->header, FOURCC_tkhd, 0, 0, 0, flags); + + tkhd->creation_time = tkhd->modification_time = get_current_qt_time (); + tkhd->duration = 0; + tkhd->track_ID = 0; + tkhd->reserved = 0; + + tkhd->reserved2[0] = tkhd->reserved2[1] = 0; + tkhd->layer = 0; + tkhd->alternate_group = 0; + tkhd->volume = 0; + tkhd->reserved3 = 0; + memset (tkhd->matrix, 0, sizeof (guint32) * 9); + tkhd->matrix[0] = 1 << 16; + tkhd->matrix[4] = 1 << 16; + tkhd->matrix[8] = 16384 << 16; + tkhd->width = 0; + tkhd->height = 0; +} + +static void +atom_tkhd_clear (AtomTKHD * tkhd) +{ + atom_full_clear (&tkhd->header); +} + +static void +atom_trak_init (AtomTRAK * trak, AtomsContext * context) +{ + atom_header_set (&trak->header, FOURCC_trak, 0, 0); + + atom_tkhd_init (&trak->tkhd, context); + atom_mdia_init (&trak->mdia, context); +} + +AtomTRAK * +atom_trak_new (AtomsContext * context) +{ + AtomTRAK *trak = g_new0 (AtomTRAK, 1); + + atom_trak_init (trak, context); + return trak; +} + +static void +atom_trak_free (AtomTRAK * trak) +{ + atom_clear (&trak->header); + atom_tkhd_clear (&trak->tkhd); + atom_mdia_clear (&trak->mdia); + g_free (trak); +} + +static void +atom_ilst_init (AtomILST * ilst) +{ + atom_header_set (&ilst->header, FOURCC_ilst, 0, 0); + ilst->entries = NULL; +} + +static AtomILST * +atom_ilst_new () +{ + AtomILST *ilst = g_new0 (AtomILST, 1); + + atom_ilst_init (ilst); + return ilst; +} + +static void +atom_ilst_free (AtomILST * ilst) +{ + if (ilst->entries) + atom_info_list_free (ilst->entries); + atom_clear (&ilst->header); + g_free (ilst); +} + +static void +atom_meta_init (AtomMETA * meta) +{ + guint8 flags[3] = { 0, 0, 0 }; + + atom_full_init (&meta->header, FOURCC_meta, 0, 0, 0, flags); + atom_hdlr_init (&meta->hdlr); + /* FIXME (ISOM says this is always 0) */ + meta->hdlr.component_type = FOURCC_mhlr; + meta->hdlr.handler_type = FOURCC_mdir; + meta->ilst = NULL; +} + +static AtomMETA * +atom_meta_new () +{ + AtomMETA *meta = g_new0 (AtomMETA, 1); + + atom_meta_init (meta); + return meta; +} + +static void +atom_meta_free (AtomMETA * meta) +{ + atom_full_clear (&meta->header); + atom_hdlr_clear (&meta->hdlr); + atom_ilst_free (meta->ilst); + meta->ilst = NULL; + g_free (meta); +} + +static void +atom_udta_init (AtomUDTA * udta) +{ + atom_header_set (&udta->header, FOURCC_udta, 0, 0); + udta->meta = NULL; +} + +static AtomUDTA * +atom_udta_new () +{ + AtomUDTA *udta = g_new0 (AtomUDTA, 1); + + atom_udta_init (udta); + return udta; +} + +static void +atom_udta_free (AtomUDTA * udta) +{ + atom_clear (&udta->header); + atom_meta_free (udta->meta); + udta->meta = NULL; + g_free (udta); +} + +static void +atom_tag_data_init (AtomTagData * data) +{ + guint8 flags[] = { 0, 0, 0 }; + + atom_full_init (&data->header, FOURCC_data, 0, 0, 0, flags); +} + +static void +atom_tag_data_clear (AtomTagData * data) +{ + atom_full_clear (&data->header); + g_free (data->data); + data->datalen = 0; +} + +/* + * Fourcc is the tag fourcc + * flags will be truncated to 24bits + */ +static AtomTag * +atom_tag_new (guint32 fourcc, guint32 flags_as_uint) +{ + AtomTag *tag = g_new0 (AtomTag, 1); + + tag->header.type = fourcc; + atom_tag_data_init (&tag->data); + tag->data.header.flags[2] = flags_as_uint & 0xFF; + tag->data.header.flags[1] = flags_as_uint & 0xFF00; + tag->data.header.flags[0] = flags_as_uint & 0xFF0000; + return tag; +} + +static void +atom_tag_free (AtomTag * tag) +{ + atom_clear (&tag->header); + atom_tag_data_clear (&tag->data); + g_free (tag); +} + +static void +atom_mvhd_init (AtomMVHD * mvhd) +{ + guint8 flags[3] = { 0, 0, 0 }; + + atom_full_init (&(mvhd->header), FOURCC_mvhd, sizeof (AtomMVHD), 0, 0, flags); + + common_time_info_init (&mvhd->time_info); + + mvhd->prefered_rate = 1 << 16; + mvhd->volume = 1 << 8; + mvhd->reserved3 = 0; + memset (mvhd->reserved4, 0, sizeof (guint32[2])); + + memset (mvhd->matrix, 0, sizeof (guint32[9])); + mvhd->matrix[0] = 1 << 16; + mvhd->matrix[4] = 1 << 16; + mvhd->matrix[8] = 16384 << 16; + + mvhd->preview_time = 0; + mvhd->preview_duration = 0; + mvhd->poster_time = 0; + mvhd->selection_time = 0; + mvhd->selection_duration = 0; + mvhd->current_time = 0; + + mvhd->next_track_id = 1; +} + +static void +atom_mvhd_clear (AtomMVHD * mvhd) +{ + atom_full_clear (&mvhd->header); +} + +static void +atom_moov_init (AtomMOOV * moov, AtomsContext * context) +{ + atom_header_set (&(moov->header), FOURCC_moov, 0, 0); + atom_mvhd_init (&(moov->mvhd)); + moov->udta = NULL; + moov->traks = NULL; +} + +AtomMOOV * +atom_moov_new (AtomsContext * context) +{ + AtomMOOV *moov = g_new0 (AtomMOOV, 1); + + atom_moov_init (moov, context); + return moov; +} + +void +atom_moov_free (AtomMOOV * moov) +{ + GList *walker; + + atom_clear (&moov->header); + atom_mvhd_clear (&moov->mvhd); + + walker = moov->traks; + while (walker) { + GList *aux = walker; + + walker = g_list_next (walker); + moov->traks = g_list_remove_link (moov->traks, aux); + atom_trak_free ((AtomTRAK *) aux->data); + g_list_free (aux); + } + + if (moov->udta) { + atom_udta_free (moov->udta); + moov->udta = NULL; + } + + g_free (moov); +} + +/* -- end of init / free -- */ + +/* -- copy data functions -- */ + +static guint8 +atom_full_get_version (AtomFull * full) +{ + return full->version; +} + +static guint64 +common_time_info_copy_data (TimeInfo * ti, gboolean trunc_to_32, + guint8 ** buffer, guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + + if (trunc_to_32) { + prop_copy_uint32 ((guint32) ti->creation_time, buffer, size, offset); + prop_copy_uint32 ((guint32) ti->modification_time, buffer, size, offset); + prop_copy_uint32 (ti->timescale, buffer, size, offset); + prop_copy_uint32 ((guint32) ti->duration, buffer, size, offset); + } else { + prop_copy_uint64 (ti->creation_time, buffer, size, offset); + prop_copy_uint64 (ti->modification_time, buffer, size, offset); + prop_copy_uint32 (ti->timescale, buffer, size, offset); + prop_copy_uint64 (ti->duration, buffer, size, offset); + } + return *offset - original_offset; +} + +static void +atom_write_size (guint8 ** buffer, guint64 * size, guint64 * offset, + guint64 atom_pos) +{ + /* this only works for non-extended atom size, which is OK + * (though it could be made to do mem_move, etc and write extended size) */ + prop_copy_uint32 (*offset - atom_pos, buffer, size, &atom_pos); +} + +guint64 +atom_copy_data (Atom * atom, guint8 ** buffer, guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + + /* copies type and size */ + prop_copy_uint32 (atom->size, buffer, size, offset); + prop_copy_fourcc (atom->type, buffer, size, offset); + + /* extended size needed */ + if (atom->size == 1) { + /* really should not happen other than with mdat atom; + * 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; +} + +static guint64 +atom_full_copy_data (AtomFull * atom, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_copy_data (&atom->header, buffer, size, offset)) { + return 0; + } + + prop_copy_uint8 (atom->version, buffer, size, offset); + prop_copy_uint8_array (atom->flags, 3, buffer, size, offset); + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_info_list_copy_data (GList * ai, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + while (ai) { + AtomInfo *info = (AtomInfo *) ai->data; + + if (!info->copy_data_func (info->atom, buffer, size, offset)) { + return 0; + } + ai = g_list_next (ai); + } + + return *offset - original_offset; +} + +static guint64 +atom_data_copy_data (AtomData * data, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_copy_data (&data->header, buffer, size, offset)) { + return 0; + } + if (data->datalen) + prop_copy_uint8_array (data->data, data->datalen, buffer, size, offset); + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +guint64 +atom_ftyp_copy_data (AtomFTYP * ftyp, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_copy_data (&ftyp->header, buffer, size, offset)) { + return 0; + } + prop_copy_fourcc (ftyp->major_brand, buffer, size, offset); + prop_copy_uint32 (ftyp->version, buffer, size, offset); + + prop_copy_fourcc_array (ftyp->compatible_brands, ftyp->compatible_brands_size, + buffer, size, offset); + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_mvhd_copy_data (AtomMVHD * atom, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint8 version; + guint64 original_offset = *offset; + + if (!atom_full_copy_data (&(atom->header), buffer, size, offset)) { + return 0; + } + + version = atom_full_get_version (&(atom->header)); + if (version == 0) { + common_time_info_copy_data (&atom->time_info, TRUE, buffer, size, offset); + } else if (version == 1) { + common_time_info_copy_data (&atom->time_info, FALSE, buffer, size, offset); + } else { + *offset = original_offset; + return 0; + } + + prop_copy_uint32 (atom->prefered_rate, buffer, size, offset); + prop_copy_uint16 (atom->volume, buffer, size, offset); + prop_copy_uint16 (atom->reserved3, buffer, size, offset); + prop_copy_uint32_array (atom->reserved4, 2, buffer, size, offset); + prop_copy_uint32_array (atom->matrix, 9, buffer, size, offset); + prop_copy_uint32 (atom->preview_time, buffer, size, offset); + prop_copy_uint32 (atom->preview_duration, buffer, size, offset); + prop_copy_uint32 (atom->poster_time, buffer, size, offset); + prop_copy_uint32 (atom->selection_time, buffer, size, offset); + prop_copy_uint32 (atom->selection_duration, buffer, size, offset); + prop_copy_uint32 (atom->current_time, buffer, size, offset); + + prop_copy_uint32 (atom->next_track_id, buffer, size, offset); + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_tkhd_copy_data (AtomTKHD * tkhd, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_full_copy_data (&tkhd->header, buffer, size, offset)) { + return 0; + } + + if (atom_full_get_version (&tkhd->header) == 0) { + prop_copy_uint32 ((guint32) tkhd->creation_time, buffer, size, offset); + prop_copy_uint32 ((guint32) tkhd->modification_time, buffer, size, offset); + prop_copy_uint32 (tkhd->track_ID, buffer, size, offset); + prop_copy_uint32 (tkhd->reserved, buffer, size, offset); + prop_copy_uint32 ((guint32) tkhd->duration, buffer, size, offset); + } else { + prop_copy_uint64 (tkhd->creation_time, buffer, size, offset); + prop_copy_uint64 (tkhd->modification_time, buffer, size, offset); + prop_copy_uint32 (tkhd->track_ID, buffer, size, offset); + prop_copy_uint32 (tkhd->reserved, buffer, size, offset); + prop_copy_uint64 (tkhd->duration, buffer, size, offset); + } + + prop_copy_uint32_array (tkhd->reserved2, 2, buffer, size, offset); + prop_copy_uint16 (tkhd->layer, buffer, size, offset); + prop_copy_uint16 (tkhd->alternate_group, buffer, size, offset); + prop_copy_uint16 (tkhd->volume, buffer, size, offset); + prop_copy_uint16 (tkhd->reserved3, buffer, size, offset); + prop_copy_uint32_array (tkhd->matrix, 9, buffer, size, offset); + + prop_copy_uint32 (tkhd->width, buffer, size, offset); + prop_copy_uint32 (tkhd->height, buffer, size, offset); + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_hdlr_copy_data (AtomHDLR * hdlr, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_full_copy_data (&hdlr->header, buffer, size, offset)) { + return 0; + } + + prop_copy_fourcc (hdlr->component_type, buffer, size, offset); + prop_copy_fourcc (hdlr->handler_type, buffer, size, offset); + prop_copy_fourcc (hdlr->manufacturer, buffer, size, offset); + prop_copy_uint32 (hdlr->flags, buffer, size, offset); + prop_copy_uint32 (hdlr->flags_mask, buffer, size, offset); + + prop_copy_null_terminated_string (hdlr->name, buffer, size, offset); + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_vmhd_copy_data (AtomVMHD * vmhd, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_full_copy_data (&vmhd->header, buffer, size, offset)) { + return 0; + } + prop_copy_uint16 (vmhd->graphics_mode, buffer, size, offset); + prop_copy_uint16_array (vmhd->opcolor, 3, buffer, size, offset); + + atom_write_size (buffer, size, offset, original_offset); + return original_offset - *offset; +} + +static guint64 +atom_smhd_copy_data (AtomSMHD * smhd, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_full_copy_data (&smhd->header, buffer, size, offset)) { + return 0; + } + prop_copy_uint16 (smhd->balance, buffer, size, offset); + prop_copy_uint16 (smhd->reserved, buffer, size, offset); + + atom_write_size (buffer, size, offset, original_offset); + return original_offset - *offset; +} + +static guint64 +atom_hmhd_copy_data (AtomHMHD * hmhd, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_full_copy_data (&hmhd->header, buffer, size, offset)) { + return 0; + } + prop_copy_uint16 (hmhd->max_pdu_size, buffer, size, offset); + prop_copy_uint16 (hmhd->avg_pdu_size, buffer, size, offset); + prop_copy_uint32 (hmhd->max_bitrate, buffer, size, offset); + prop_copy_uint32 (hmhd->avg_bitrate, buffer, size, offset); + prop_copy_uint32 (hmhd->sliding_avg_bitrate, buffer, size, offset); + + atom_write_size (buffer, size, offset, original_offset); + return original_offset - *offset; +} + +static gboolean +atom_url_same_file_flag (AtomURL * url) +{ + return (url->header.flags[2] & 0x1) == 1; +} + +static guint64 +atom_url_copy_data (AtomURL * url, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_full_copy_data (&url->header, buffer, size, offset)) { + return 0; + } + + if (!atom_url_same_file_flag (url)) { + prop_copy_null_terminated_string (url->location, buffer, size, offset); + } + + atom_write_size (buffer, size, offset, original_offset); + return original_offset - *offset; +} + +static guint64 +atom_stts_copy_data (AtomSTTS * stts, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + GList *walker; + + if (!atom_full_copy_data (&stts->header, buffer, size, offset)) { + return 0; + } + + prop_copy_uint32 (stts->n_entries, buffer, size, offset); + /* minimize realloc */ + prop_copy_ensure_buffer (buffer, size, offset, 8 * stts->n_entries); + for (walker = g_list_last (stts->entries); walker != NULL; + walker = g_list_previous (walker)) { + STTSEntry *entry = (STTSEntry *) walker->data; + + prop_copy_uint32 (entry->sample_count, buffer, size, offset); + prop_copy_int32 (entry->sample_delta, buffer, size, offset); + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_sample_entry_copy_data (SampleTableEntry * se, guint8 ** buffer, + guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_copy_data (&se->header, buffer, size, offset)) { + return 0; + } + + prop_copy_uint8_array (se->reserved, 6, buffer, size, offset); + prop_copy_uint16 (se->data_reference_index, buffer, size, offset); + + return *offset - original_offset; +} + +static guint64 +atom_esds_copy_data (AtomESDS * esds, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_full_copy_data (&esds->header, buffer, size, offset)) { + return 0; + } + if (!desc_es_descriptor_copy_data (&esds->es, buffer, size, offset)) { + return 0; + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_frma_copy_data (AtomFRMA * frma, guint8 ** buffer, + guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_copy_data (&(frma->header), buffer, size, offset)) + return 0; + + prop_copy_fourcc (frma->media_type, buffer, size, offset); + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_mp4s_copy_data (SampleTableEntryMP4S * mp4s, guint8 ** buffer, + guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_sample_entry_copy_data (&mp4s->se, buffer, size, offset)) { + return 0; + } + if (!atom_esds_copy_data (&mp4s->es, buffer, size, offset)) { + return 0; + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_hint_sample_entry_copy_data (AtomHintSampleEntry * hse, guint8 ** buffer, + guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_sample_entry_copy_data (&hse->se, buffer, size, offset)) { + return 0; + } + + prop_copy_uint32 (hse->size, buffer, size, offset); + prop_copy_uint8_array (hse->data, hse->size, buffer, size, offset); + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +sample_entry_mp4a_copy_data (SampleTableEntryMP4A * mp4a, guint8 ** buffer, + guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_sample_entry_copy_data (&mp4a->se, buffer, size, offset)) { + return 0; + } + + prop_copy_uint16 (mp4a->version, buffer, size, offset); + prop_copy_uint16 (mp4a->revision_level, buffer, size, offset); + prop_copy_uint32 (mp4a->vendor, buffer, size, offset); + prop_copy_uint16 (mp4a->channels, buffer, size, offset); + prop_copy_uint16 (mp4a->sample_size, buffer, size, offset); + prop_copy_uint16 (mp4a->compression_id, buffer, size, offset); + prop_copy_uint16 (mp4a->packet_size, buffer, size, offset); + prop_copy_uint32 (mp4a->sample_rate, buffer, size, offset); + + /* this should always be 0 for mp4 flavor */ + if (mp4a->version == 1) { + prop_copy_uint32 (mp4a->samples_per_packet, buffer, size, offset); + prop_copy_uint32 (mp4a->bytes_per_packet, buffer, size, offset); + prop_copy_uint32 (mp4a->bytes_per_frame, buffer, size, offset); + prop_copy_uint32 (mp4a->bytes_per_sample, buffer, size, offset); + } + + if (mp4a->extension_atoms) { + if (!atom_info_list_copy_data (mp4a->extension_atoms, buffer, size, offset)) + return 0; + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +sample_entry_mp4v_copy_data (SampleTableEntryMP4V * mp4v, guint8 ** buffer, + guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_sample_entry_copy_data (&mp4v->se, buffer, size, offset)) { + return 0; + } + + prop_copy_uint16 (mp4v->version, buffer, size, offset); + prop_copy_uint16 (mp4v->revision_level, buffer, size, offset); + prop_copy_fourcc (mp4v->vendor, buffer, size, offset); + prop_copy_uint32 (mp4v->temporal_quality, buffer, size, offset); + prop_copy_uint32 (mp4v->spatial_quality, buffer, size, offset); + + prop_copy_uint16 (mp4v->width, buffer, size, offset); + prop_copy_uint16 (mp4v->height, buffer, size, offset); + + prop_copy_uint32 (mp4v->horizontal_resolution, buffer, size, offset); + prop_copy_uint32 (mp4v->vertical_resolution, buffer, size, offset); + prop_copy_uint32 (mp4v->datasize, buffer, size, offset); + + prop_copy_uint16 (mp4v->frame_count, buffer, size, offset); + + prop_copy_fixed_size_string ((guint8 *) mp4v->compressor, 32, buffer, size, + offset); + + prop_copy_uint16 (mp4v->depth, buffer, size, offset); + prop_copy_uint16 (mp4v->color_table_id, buffer, size, offset); + + /* extra atoms */ + if (mp4v->extension_atoms && + !atom_info_list_copy_data (mp4v->extension_atoms, buffer, size, offset)) + return 0; + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_stsz_copy_data (AtomSTSZ * stsz, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + GList *walker; + + if (!atom_full_copy_data (&stsz->header, buffer, size, offset)) { + return 0; + } + + prop_copy_uint32 (stsz->sample_size, buffer, size, offset); + prop_copy_uint32 (stsz->table_size, buffer, size, offset); + /* minimize realloc */ + prop_copy_ensure_buffer (buffer, size, offset, 4 * stsz->table_size); + if (stsz->sample_size == 0) { + for (walker = g_list_last (stsz->entries); walker != NULL; + walker = g_list_previous (walker)) { + prop_copy_uint32 ((guint32) GPOINTER_TO_UINT (walker->data), buffer, size, + offset); + } + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_stsc_copy_data (AtomSTSC * stsc, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + GList *walker; + + if (!atom_full_copy_data (&stsc->header, buffer, size, offset)) { + return 0; + } + + prop_copy_uint32 (stsc->n_entries, buffer, size, offset); + /* minimize realloc */ + prop_copy_ensure_buffer (buffer, size, offset, 12 * stsc->n_entries); + + for (walker = g_list_last (stsc->entries); walker != NULL; + walker = g_list_previous (walker)) { + STSCEntry *entry = (STSCEntry *) walker->data; + + prop_copy_uint32 (entry->first_chunk, buffer, size, offset); + prop_copy_uint32 (entry->samples_per_chunk, buffer, size, offset); + prop_copy_uint32 (entry->sample_description_index, buffer, size, offset); + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_ctts_copy_data (AtomCTTS * ctts, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + GList *walker; + + if (!atom_full_copy_data (&ctts->header, buffer, size, offset)) { + return 0; + } + + prop_copy_uint32 (ctts->n_entries, buffer, size, offset); + /* minimize realloc */ + prop_copy_ensure_buffer (buffer, size, offset, 8 * ctts->n_entries); + for (walker = g_list_last (ctts->entries); walker != NULL; + walker = g_list_previous (walker)) { + CTTSEntry *entry = (CTTSEntry *) walker->data; + + prop_copy_uint32 (entry->samplecount, buffer, size, offset); + prop_copy_uint32 (entry->sampleoffset, buffer, size, offset); + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_stco64_copy_data (AtomSTCO64 * stco64, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + GList *walker; + gboolean trunc_to_32 = stco64->header.header.type == FOURCC_stco; + + if (!atom_full_copy_data (&stco64->header, buffer, size, offset)) { + return 0; + } + + prop_copy_uint32 (stco64->n_entries, buffer, size, offset); + + /* minimize realloc */ + prop_copy_ensure_buffer (buffer, size, offset, 8 * stco64->n_entries); + for (walker = g_list_last (stco64->entries); walker != NULL; + walker = g_list_previous (walker)) { + guint64 *value = (guint64 *) walker->data; + + if (trunc_to_32) { + prop_copy_uint32 ((guint32) * value, buffer, size, offset); + } else { + prop_copy_uint64 (*value, buffer, size, offset); + } + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_stss_copy_data (AtomSTSS * stss, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + GList *walker; + + if (stss->entries == NULL) { + /* FIXME not needing this atom might be confused with error while copying */ + return 0; + } + + if (!atom_full_copy_data (&stss->header, buffer, size, offset)) { + return 0; + } + + prop_copy_uint32 (stss->n_entries, buffer, size, offset); + /* minimize realloc */ + prop_copy_ensure_buffer (buffer, size, offset, 4 * stss->n_entries); + for (walker = g_list_last (stss->entries); walker != NULL; + walker = g_list_previous (walker)) { + prop_copy_uint32 ((guint32) GPOINTER_TO_UINT (walker->data), buffer, size, + offset); + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_stsd_copy_data (AtomSTSD * stsd, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + GList *walker; + + if (!atom_full_copy_data (&stsd->header, buffer, size, offset)) { + return 0; + } + + prop_copy_uint32 (stsd->n_entries, buffer, size, offset); + + for (walker = g_list_last (stsd->entries); walker != NULL; + walker = g_list_previous (walker)) { + SampleTableEntry *se = (SampleTableEntry *) walker->data; + + switch (((Atom *) walker->data)->type) { + case FOURCC_mp4a: + if (!sample_entry_mp4a_copy_data ((SampleTableEntryMP4A *) walker->data, + buffer, size, offset)) { + return 0; + } + break; + case FOURCC_mp4s: + if (!atom_mp4s_copy_data ((SampleTableEntryMP4S *) walker->data, + buffer, size, offset)) { + return 0; + } + break; + case FOURCC_mp4v: + if (!sample_entry_mp4v_copy_data ((SampleTableEntryMP4V *) walker->data, + buffer, size, offset)) { + return 0; + } + break; + default: + if (se->kind == VIDEO) { + size += + sample_entry_mp4v_copy_data ((SampleTableEntryMP4V *) walker-> + data, buffer, size, offset); + } else if (se->kind == AUDIO) { + size += + sample_entry_mp4a_copy_data ((SampleTableEntryMP4A *) walker-> + data, buffer, size, offset); + } else { + if (!atom_hint_sample_entry_copy_data ( + (AtomHintSampleEntry *) walker->data, buffer, size, offset)) { + return 0; + } + } + break; + } + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_stbl_copy_data (AtomSTBL * stbl, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_copy_data (&stbl->header, buffer, size, offset)) { + return 0; + } + + if (!atom_stsd_copy_data (&stbl->stsd, buffer, size, offset)) { + return 0; + } + if (!atom_stts_copy_data (&stbl->stts, buffer, size, offset)) { + return 0; + } + /* this atom is optional, so let's check if we need it + * (to avoid false error) */ + if (stbl->stss.entries) { + if (!atom_stss_copy_data (&stbl->stss, buffer, size, offset)) { + return 0; + } + } + + if (!atom_stsc_copy_data (&stbl->stsc, buffer, size, offset)) { + return 0; + } + if (!atom_stsz_copy_data (&stbl->stsz, buffer, size, offset)) { + return 0; + } + if (stbl->ctts) { + if (!atom_ctts_copy_data (stbl->ctts, buffer, size, offset)) { + return 0; + } + } + if (!atom_stco64_copy_data (&stbl->stco64, buffer, size, offset)) { + return 0; + } + + atom_write_size (buffer, size, offset, original_offset); + return original_offset - *offset; +} + + +static guint64 +atom_dref_copy_data (AtomDREF * dref, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + GList *walker; + + if (!atom_full_copy_data (&dref->header, buffer, size, offset)) { + return 0; + } + + prop_copy_uint32 (g_list_length (dref->entries), buffer, size, offset); + + walker = dref->entries; + while (walker != NULL) { + Atom *atom = (Atom *) walker->data; + + if (atom->type == FOURCC_url_) { + atom_url_copy_data ((AtomURL *) atom, buffer, size, offset); + } else if (atom->type == FOURCC_alis) { + atom_full_copy_data ((AtomFull *) atom, buffer, size, offset); + } else { + g_error ("Unsupported atom used inside dref atom"); + } + walker = g_list_next (walker); + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_dinf_copy_data (AtomDINF * dinf, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_copy_data (&dinf->header, buffer, size, offset)) { + return 0; + } + + if (!atom_dref_copy_data (&dinf->dref, buffer, size, offset)) { + return 0; + } + + atom_write_size (buffer, size, offset, original_offset); + return original_offset - *offset; +} + +static guint64 +atom_minf_copy_data (AtomMINF * minf, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_copy_data (&minf->header, buffer, size, offset)) { + return 0; + } + + if (minf->vmhd) { + if (!atom_vmhd_copy_data (minf->vmhd, buffer, size, offset)) { + return 0; + } + } else if (minf->smhd) { + if (!atom_smhd_copy_data (minf->smhd, buffer, size, offset)) { + return 0; + } + } else if (minf->hmhd) { + if (!atom_hmhd_copy_data (minf->hmhd, buffer, size, offset)) { + return 0; + } + } + + if (minf->hdlr) { + if (!atom_hdlr_copy_data (minf->hdlr, buffer, size, offset)) { + return 0; + } + } + + if (!atom_dinf_copy_data (&minf->dinf, buffer, size, offset)) { + return 0; + } + if (!atom_stbl_copy_data (&minf->stbl, buffer, size, offset)) { + return 0; + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_mdhd_copy_data (AtomMDHD * mdhd, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_full_copy_data (&mdhd->header, buffer, size, offset)) { + return 0; + } + + if (!common_time_info_copy_data (&mdhd->time_info, + atom_full_get_version (&mdhd->header) == 0, buffer, size, offset)) { + return 0; + } + + prop_copy_uint16 (mdhd->language_code, buffer, size, offset); + prop_copy_uint16 (mdhd->quality, buffer, size, offset); + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_mdia_copy_data (AtomMDIA * mdia, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_copy_data (&mdia->header, buffer, size, offset)) { + return 0; + } + if (!atom_mdhd_copy_data (&mdia->mdhd, buffer, size, offset)) { + return 0; + } + if (!atom_hdlr_copy_data (&mdia->hdlr, buffer, size, offset)) { + return 0; + } + + if (!atom_minf_copy_data (&mdia->minf, buffer, size, offset)) { + return 0; + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_trak_copy_data (AtomTRAK * trak, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_copy_data (&trak->header, buffer, size, offset)) { + return 0; + } + if (!atom_tkhd_copy_data (&trak->tkhd, buffer, size, offset)) { + return 0; + } + + if (!atom_mdia_copy_data (&trak->mdia, buffer, size, offset)) { + return 0; + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_tag_data_copy_data (AtomTagData * data, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_full_copy_data (&data->header, buffer, size, offset)) { + return 0; + } + + prop_copy_uint32 (data->reserved, buffer, size, offset); + prop_copy_uint8_array (data->data, data->datalen, buffer, size, offset); + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_tag_copy_data (AtomTag * tag, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_copy_data (&tag->header, buffer, size, offset)) { + return 0; + } + + if (!atom_tag_data_copy_data (&tag->data, buffer, size, offset)) { + return 0; + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_ilst_copy_data (AtomILST * ilst, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_copy_data (&ilst->header, buffer, size, offset)) { + return 0; + } + /* extra atoms */ + if (ilst->entries && + !atom_info_list_copy_data (ilst->entries, buffer, size, offset)) + return 0; + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_meta_copy_data (AtomMETA * meta, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_full_copy_data (&meta->header, buffer, size, offset)) { + return 0; + } + if (!atom_hdlr_copy_data (&meta->hdlr, buffer, size, offset)) { + return 0; + } + if (meta->ilst) { + if (!atom_ilst_copy_data (meta->ilst, buffer, size, offset)) { + return 0; + } + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_udta_copy_data (AtomUDTA * udta, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_copy_data (&udta->header, buffer, size, offset)) { + return 0; + } + if (udta->meta) { + if (!atom_meta_copy_data (udta->meta, buffer, size, offset)) { + return 0; + } + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +guint64 +atom_moov_copy_data (AtomMOOV * atom, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + GList *walker; + + if (!atom_copy_data (&(atom->header), buffer, size, offset)) + return 0; + + if (!atom_mvhd_copy_data (&(atom->mvhd), buffer, size, offset)) + return 0; + + walker = g_list_first (atom->traks); + while (walker != NULL) { + if (!atom_trak_copy_data ((AtomTRAK *) walker->data, buffer, size, offset)) { + return 0; + } + walker = g_list_next (walker); + } + + if (atom->udta) { + if (!atom_udta_copy_data (atom->udta, buffer, size, offset)) { + return 0; + } + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +static guint64 +atom_wave_copy_data (AtomWAVE * wave, guint8 ** buffer, + guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!atom_copy_data (&(wave->header), buffer, size, offset)) + return 0; + + if (wave->extension_atoms) { + if (!atom_info_list_copy_data (wave->extension_atoms, buffer, size, offset)) + return 0; + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + +/* -- end of copy data functions -- */ + +/* -- general functions, API and support functions */ + +/* add samples to tables */ + +static STSCEntry * +stsc_entry_new (guint32 first_chunk, guint32 samples, guint32 desc_index) +{ + STSCEntry *entry = g_new0 (STSCEntry, 1); + + entry->first_chunk = first_chunk; + entry->samples_per_chunk = samples; + entry->sample_description_index = desc_index; + return entry; +} + +static void +atom_stsc_add_new_entry (AtomSTSC * stsc, guint32 first_chunk, guint32 nsamples) +{ + stsc->entries = + g_list_prepend (stsc->entries, stsc_entry_new (first_chunk, nsamples, 1)); + stsc->n_entries++; +} + +static STTSEntry * +stts_entry_new (guint32 sample_count, gint32 sample_delta) +{ + STTSEntry *entry = g_new0 (STTSEntry, 1); + + entry->sample_count = sample_count; + entry->sample_delta = sample_delta; + return entry; +} + +static void +atom_stts_add_entry (AtomSTTS * stts, guint32 sample_count, gint32 sample_delta) +{ + STTSEntry *entry; + + if (stts->entries == NULL) { + stts->entries = + g_list_prepend (stts->entries, stts_entry_new (sample_count, + sample_delta)); + stts->n_entries++; + return; + } + entry = (STTSEntry *) g_list_first (stts->entries)->data; + if (entry->sample_delta == sample_delta) { + entry->sample_count += sample_count; + } else { + stts->entries = + g_list_prepend (stts->entries, stts_entry_new (sample_count, + sample_delta)); + stts->n_entries++; + } +} + +static void +atom_stsz_add_entry (AtomSTSZ * stsz, guint32 nsamples, guint32 size) +{ + guint32 i; + + stsz->table_size += nsamples; + if (stsz->sample_size != 0) { + /* it is constant size, we don't need entries */ + return; + } + for (i = 0; i < nsamples; i++) { + stsz->entries = g_list_prepend (stsz->entries, GUINT_TO_POINTER (size)); + } +} + +static guint32 +atom_stco64_get_entry_count (AtomSTCO64 * stco64) +{ + return stco64->n_entries; +} + +static void +atom_stco64_add_entry (AtomSTCO64 * stco64, guint64 entry) +{ + guint64 *pont = g_new (guint64, 1); + + *pont = entry; + stco64->entries = g_list_prepend (stco64->entries, pont); + stco64->n_entries++; +} + +static void +atom_stss_add_entry (AtomSTSS * stss, guint32 sample) +{ + stss->entries = g_list_prepend (stss->entries, GUINT_TO_POINTER (sample)); + stss->n_entries++; +} + +static void +atom_stbl_add_stss_entry (AtomSTBL * stbl) +{ + guint32 sample_index = stbl->stsz.table_size; + + atom_stss_add_entry (&stbl->stss, sample_index); +} + +static void +atom_ctts_add_entry (AtomCTTS * ctts, guint32 nsamples, guint32 offset) +{ + GList *walker; + CTTSEntry *entry; + + walker = g_list_first (ctts->entries); + entry = (walker == NULL) ? NULL : (CTTSEntry *) walker->data; + + if (entry == NULL || entry->sampleoffset != offset) { + CTTSEntry *entry = g_new0 (CTTSEntry, 1); + + entry->samplecount = nsamples; + entry->sampleoffset = offset; + ctts->entries = g_list_prepend (ctts->entries, entry); + ctts->n_entries++; + } else { + entry->samplecount += nsamples; + } +} + +static void +atom_stbl_add_ctts_entry (AtomSTBL * stbl, guint32 nsamples, guint32 offset) +{ + if (stbl->ctts == NULL) { + stbl->ctts = atom_ctts_new (); + } + atom_ctts_add_entry (stbl->ctts, nsamples, offset); +} + +void +atom_trak_add_samples (AtomTRAK * trak, guint32 nsamples, guint32 delta, + guint32 size, guint64 chunk_offset, gboolean sync, + gboolean do_pts, gint64 pts_offset) +{ + AtomSTBL *stbl = &trak->mdia.minf.stbl; + + atom_stts_add_entry (&stbl->stts, nsamples, delta); + atom_stsz_add_entry (&stbl->stsz, nsamples, size); + atom_stco64_add_entry (&stbl->stco64, chunk_offset); + atom_stsc_add_new_entry (&stbl->stsc, + atom_stco64_get_entry_count (&stbl->stco64), nsamples); + if (sync) + atom_stbl_add_stss_entry (stbl); + if (do_pts) + atom_stbl_add_ctts_entry (stbl, nsamples, pts_offset); +} + +/* trak and moov molding */ + +guint32 +atom_trak_get_timescale (AtomTRAK * trak) +{ + return trak->mdia.mdhd.time_info.timescale; +} + +static void +atom_trak_set_id (AtomTRAK * trak, guint32 id) +{ + trak->tkhd.track_ID = id; +} + +void +atom_moov_add_trak (AtomMOOV * moov, AtomTRAK * trak) +{ + atom_trak_set_id (trak, moov->mvhd.next_track_id++); + moov->traks = g_list_append (moov->traks, trak); +} + +static guint64 +atom_trak_get_duration (AtomTRAK * trak) +{ + return trak->tkhd.duration; +} + +static guint64 +atom_stts_get_total_duration (AtomSTTS * stts) +{ + GList *walker = stts->entries; + guint64 sum = 0; + + while (walker) { + STTSEntry *entry = (STTSEntry *) walker->data; + + sum += (guint64) (entry->sample_count) * entry->sample_delta; + walker = g_list_next (walker); + } + return sum; +} + +static void +atom_trak_update_duration (AtomTRAK * trak, guint64 moov_timescale) +{ + trak->mdia.mdhd.time_info.duration = + atom_stts_get_total_duration (&trak->mdia.minf.stbl.stts); + trak->tkhd.duration = + gst_util_uint64_scale (trak->mdia.mdhd.time_info.duration, moov_timescale, + trak->mdia.mdhd.time_info.timescale); +} + +static guint32 +atom_moov_get_timescale (AtomMOOV * moov) +{ + return moov->mvhd.time_info.timescale; +} + +void +atom_moov_update_timescale (AtomMOOV * moov, guint32 timescale) +{ + moov->mvhd.time_info.timescale = timescale; +} + +void +atom_moov_update_duration (AtomMOOV * moov) +{ + GList *traks = moov->traks; + guint64 dur, duration = 0; + + 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; + traks = g_list_next (traks); + } + moov->mvhd.time_info.duration = duration; +} + +static void +atom_set_type (Atom * atom, guint32 fourcc) +{ + atom->type = fourcc; +} + +static void +atom_stbl_set_64bits (AtomSTBL * stbl, gboolean use) +{ + if (use) { + atom_set_type (&stbl->stco64.header.header, FOURCC_co64); + } else { + atom_set_type (&stbl->stco64.header.header, FOURCC_stco); + } +} + +static void +atom_trak_set_64bits (AtomTRAK * trak, gboolean use) +{ + atom_stbl_set_64bits (&trak->mdia.minf.stbl, use); +} + +void +atom_moov_set_64bits (AtomMOOV * moov, gboolean large_file) +{ + GList *traks = moov->traks; + + while (traks) { + AtomTRAK *trak = (AtomTRAK *) traks->data; + + atom_trak_set_64bits (trak, large_file); + traks = g_list_next (traks); + } +} + +static void +atom_stco64_chunks_add_offset (AtomSTCO64 * stco64, guint32 offset) +{ + GList *entries = stco64->entries; + + while (entries) { + guint64 *value = (guint64 *) entries->data; + + *value += offset; + entries = g_list_next (entries); + } +} + +void +atom_moov_chunks_add_offset (AtomMOOV * moov, guint32 offset) +{ + GList *traks = moov->traks; + + while (traks) { + AtomTRAK *trak = (AtomTRAK *) traks->data; + + atom_stco64_chunks_add_offset (&trak->mdia.minf.stbl.stco64, offset); + traks = g_list_next (traks); + } +} + +/* + * Meta tags functions + */ +static void +atom_moov_init_metatags (AtomMOOV * moov) +{ + 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 (); + } +} + +static void +atom_tag_data_alloc_data (AtomTagData * data, guint size) +{ + if (data->data != NULL) { + g_free (data->data); + } + data->data = g_new0 (guint8, size); + data->datalen = size; +} + +static void +atom_moov_append_tag (AtomMOOV * moov, AtomInfo * tag) +{ + AtomILST *ilst; + + ilst = moov->udta->meta->ilst; + ilst->entries = g_list_append (ilst->entries, tag); +} + +void +atom_moov_add_tag (AtomMOOV * moov, guint32 fourcc, guint32 flags, + const guint8 * data, guint size) +{ + AtomTag *tag; + AtomTagData *tdata; + + tag = atom_tag_new (fourcc, flags); + tdata = &tag->data; + atom_tag_data_alloc_data (tdata, size); + g_memmove (tdata->data, data, size); + + atom_moov_init_metatags (moov); + atom_moov_append_tag (moov, + build_atom_info_wrapper ((Atom *) tag, atom_tag_copy_data, + atom_tag_free)); +} + +void +atom_moov_add_str_tag (AtomMOOV * moov, guint32 fourcc, const gchar * value) +{ + gint len = strlen (value); + + if (len > 0) + atom_moov_add_tag (moov, fourcc, METADATA_TEXT_FLAG, (guint8 *) value, len); +} + +void +atom_moov_add_uint_tag (AtomMOOV * moov, guint32 fourcc, guint32 flags, + guint32 value) +{ + guint8 data[8] = { 0, }; + + if (flags) { + GST_WRITE_UINT16_BE (data, value); + atom_moov_add_tag (moov, fourcc, flags, data, 2); + } else { + GST_WRITE_UINT32_BE (data + 2, value); + atom_moov_add_tag (moov, fourcc, flags, data, 8); + } +} + +void +atom_moov_add_blob_tag (AtomMOOV * moov, guint8 * data, guint size) +{ + AtomData *data_atom; + GstBuffer *buf; + guint len; + guint32 fourcc; + + if (size < 8) + return; + + /* blob is unparsed atom; + * extract size and fourcc, and wrap remainder in data atom */ + len = GST_READ_UINT32_BE (data); + fourcc = GST_READ_UINT32_LE (data + 4); + if (len > size) + return; + + buf = gst_buffer_new (); + GST_BUFFER_SIZE (buf) = len - 8; + GST_BUFFER_DATA (buf) = data + 8; + + 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)); +} + +/* + * Functions for specifying media types + */ + +static void +atom_minf_set_audio (AtomMINF * minf) +{ + atom_minf_clear_handlers (minf); + minf->smhd = atom_smhd_new (); +} + +static void +atom_minf_set_video (AtomMINF * minf, AtomsContext * context) +{ + atom_minf_clear_handlers (minf); + minf->vmhd = atom_vmhd_new (context); +} + +static void +atom_hdlr_set_type (AtomHDLR * hdlr, AtomsContext * context, guint32 comp_type, + guint32 hdlr_type) +{ + if (context->flavor == ATOMS_TREE_FLAVOR_MOV) { + hdlr->component_type = comp_type; + } + hdlr->handler_type = hdlr_type; +} + +static void +atom_mdia_set_hdlr_type_audio (AtomMDIA * mdia, AtomsContext * context) +{ + atom_hdlr_set_type (&mdia->hdlr, context, FOURCC_mhlr, FOURCC_soun); +} + +static void +atom_mdia_set_hdlr_type_video (AtomMDIA * mdia, AtomsContext * context) +{ + atom_hdlr_set_type (&mdia->hdlr, context, FOURCC_mhlr, FOURCC_vide); +} + +static void +atom_mdia_set_audio (AtomMDIA * mdia, AtomsContext * context) +{ + atom_mdia_set_hdlr_type_audio (mdia, context); + atom_minf_set_audio (&mdia->minf); +} + +static void +atom_mdia_set_video (AtomMDIA * mdia, AtomsContext * context) +{ + atom_mdia_set_hdlr_type_video (mdia, context); + atom_minf_set_video (&mdia->minf, context); +} + +static void +atom_tkhd_set_audio (AtomTKHD * tkhd) +{ + tkhd->volume = 0x0100; + tkhd->width = tkhd->height = 0; +} + +static void +atom_tkhd_set_video (AtomTKHD * tkhd, AtomsContext * context, guint32 width, + guint32 height) +{ + tkhd->volume = 0; + + /* qt and ISO base media do not contradict, and examples agree */ + tkhd->width = width; + tkhd->height = height; +} + +/* re-negotiation is prevented at top-level, so only 1 entry expected. + * Quite some more care here and elsewhere may be needed to + * support several entries */ +static SampleTableEntryMP4A * +atom_trak_add_audio_entry (AtomTRAK * trak, AtomsContext * context, + guint32 type) +{ + AtomSTSD *stsd = &trak->mdia.minf.stbl.stsd; + SampleTableEntryMP4A *mp4a = sample_entry_mp4a_new (); + + mp4a->se.header.type = type; + mp4a->se.kind = AUDIO; + mp4a->compression_id = -1; + mp4a->se.data_reference_index = 1; + + stsd->entries = g_list_prepend (stsd->entries, mp4a); + stsd->n_entries++; + return mp4a; +} + +static SampleTableEntryMP4V * +atom_trak_add_video_entry (AtomTRAK * trak, AtomsContext * context, + guint32 type) +{ + SampleTableEntryMP4V *mp4v = sample_entry_mp4v_new (context); + AtomSTSD *stsd = &trak->mdia.minf.stbl.stsd; + + mp4v->se.header.type = type; + mp4v->se.kind = VIDEO; + mp4v->se.data_reference_index = 1; + mp4v->horizontal_resolution = 72 << 16; + mp4v->vertical_resolution = 72 << 16; + if (context->flavor == ATOMS_TREE_FLAVOR_MOV) { + mp4v->spatial_quality = 512; + mp4v->temporal_quality = 512; + } + + stsd->entries = g_list_prepend (stsd->entries, mp4v); + stsd->n_entries++; + return mp4v; +} + +static void +atom_trak_set_constant_size_samples (AtomTRAK * trak, guint32 sample_size) +{ + trak->mdia.minf.stbl.stsz.sample_size = sample_size; +} + +static void +atom_trak_set_audio (AtomTRAK * trak, AtomsContext * context) +{ + atom_tkhd_set_audio (&trak->tkhd); + atom_mdia_set_audio (&trak->mdia, context); +} + +static void +atom_trak_set_video (AtomTRAK * trak, AtomsContext * context, guint32 width, + guint32 height) +{ + atom_tkhd_set_video (&trak->tkhd, context, width, height); + atom_mdia_set_video (&trak->mdia, context); +} + +static void +atom_trak_set_audio_commons (AtomTRAK * trak, AtomsContext * context, + guint32 rate) +{ + atom_trak_set_audio (trak, context); + trak->mdia.mdhd.time_info.timescale = rate; +} + +static void +atom_trak_set_video_commons (AtomTRAK * trak, AtomsContext * context, + guint32 rate, guint32 width, guint32 height) +{ + atom_trak_set_video (trak, context, width, height); + trak->mdia.mdhd.time_info.timescale = rate; + trak->tkhd.width = width << 16; + trak->tkhd.height = height << 16; +} + +void +atom_trak_set_audio_type (AtomTRAK * trak, AtomsContext * context, + AudioSampleEntry * entry, guint32 scale, AtomInfo * ext, gint sample_size) +{ + SampleTableEntryMP4A *ste; + + atom_trak_set_audio_commons (trak, context, scale); + ste = atom_trak_add_audio_entry (trak, context, entry->fourcc); + + ste->version = entry->version; + ste->compression_id = entry->compression_id; + ste->sample_size = entry->sample_size; + ste->sample_rate = entry->sample_rate << 16; + ste->channels = entry->channels; + + ste->samples_per_packet = entry->samples_per_packet; + ste->bytes_per_sample = entry->bytes_per_sample; + ste->bytes_per_packet = entry->bytes_per_packet; + ste->bytes_per_frame = entry->bytes_per_frame; + + if (ext) + ste->extension_atoms = g_list_prepend (ste->extension_atoms, ext); + + /* 0 size means variable size */ + atom_trak_set_constant_size_samples (trak, sample_size); +} + +void +atom_trak_set_video_type (AtomTRAK * trak, AtomsContext * context, + VisualSampleEntry * entry, guint32 scale, AtomInfo * ext) +{ + SampleTableEntryMP4V *ste; + + atom_trak_set_video_commons (trak, context, scale, entry->width, + entry->height); + ste = atom_trak_add_video_entry (trak, context, entry->fourcc); + + ste->width = entry->width; + ste->height = entry->height; + ste->depth = entry->depth; + ste->color_table_id = entry->color_table_id; + ste->frame_count = entry->frame_count; + + if (ext) + ste->extension_atoms = g_list_prepend (ste->extension_atoms, ext); +} + +/* some sample description construction helpers */ + +static AtomInfo * +build_esds_atom (guint32 track_id, guint8 object_type, guint8 stream_type, + const GstBuffer * codec_data) +{ + AtomESDS *esds; + + esds = atom_esds_new (); + esds->es.id = track_id & 0xFFFF; + esds->es.dec_conf_desc.object_type = object_type; + esds->es.dec_conf_desc.stream_type = stream_type << 2 | 0x01; + + /* optional DecoderSpecificInfo */ + if (codec_data) { + DecoderSpecificInfoDescriptor *desc; + + esds->es.dec_conf_desc.dec_specific_info = desc = + desc_dec_specific_info_new (); + desc_dec_specific_info_alloc_data (desc, GST_BUFFER_SIZE (codec_data)); + + memcpy (desc->data, GST_BUFFER_DATA (codec_data), + GST_BUFFER_SIZE (codec_data)); + } + + return build_atom_info_wrapper ((Atom *) esds, atom_esds_copy_data, + atom_esds_free); +} + +static AtomInfo * +build_mov_aac_extension (guint32 track_id, const GstBuffer * codec_data) +{ + AtomWAVE *wave; + AtomFRMA *frma; + Atom *ext_atom; + GstBuffer *buf; + + /* Add WAVE atom to the MP4A sample table entry */ + wave = atom_wave_new (); + + /* Prepend Terminator atom to the WAVE list first, so it ends up last */ + ext_atom = (Atom *) atom_data_new (FOURCC_null); + wave->extension_atoms = + atom_info_list_prepend_atom (wave->extension_atoms, (Atom *) ext_atom, + (AtomCopyDataFunc) atom_data_copy_data, (AtomFreeFunc) atom_data_free); + + /* Add ESDS atom to WAVE */ + wave->extension_atoms = g_list_prepend (wave->extension_atoms, + build_esds_atom (track_id, ESDS_OBJECT_TYPE_MPEG4_P3, + ESDS_STREAM_TYPE_AUDIO, codec_data)); + + /* Add MP4A atom to the WAVE: + * not really in spec, but makes offset based players happy */ + buf = gst_buffer_new_and_alloc (4); + *((guint32 *) GST_BUFFER_DATA (buf)) = 0; + ext_atom = (Atom *) atom_data_new_from_gst_buffer (FOURCC_mp4a, buf); + gst_buffer_unref (buf); + + wave->extension_atoms = + atom_info_list_prepend_atom (wave->extension_atoms, (Atom *) ext_atom, + (AtomCopyDataFunc) atom_data_copy_data, (AtomFreeFunc) atom_data_free); + + /* Add FRMA to the WAVE */ + frma = atom_frma_new (); + frma->media_type = FOURCC_mp4a; + + wave->extension_atoms = + atom_info_list_prepend_atom (wave->extension_atoms, (Atom *) frma, + (AtomCopyDataFunc) atom_frma_copy_data, (AtomFreeFunc) atom_frma_free); + + return build_atom_info_wrapper ((Atom *) wave, atom_wave_copy_data, + atom_wave_free); +} + +/* if applicable, construct Sample Table Entry extension atoms for a trak, + * typically wrapping codec data; + * for the given flavor of the format, fourcc type of the sample entry, + * and in case of MP4 covered streams, the ESDS type */ +AtomInfo * +build_sample_entry_extension (AtomTRAK * trak, AtomsTreeFlavor flavor, + guint32 fourcc, guint esds_type, const GstBuffer * codec_data) +{ + AtomInfo *result = NULL; + guint32 ext_fourcc = 0; + guint32 track_id; + + g_return_val_if_fail (trak != NULL, NULL); + track_id = trak->tkhd.track_ID; + + /* extension varies depending on format */ + if (flavor == ATOMS_TREE_FLAVOR_ISOM) { + guint8 stream_type = 0; + + if (fourcc == FOURCC_mp4a) + stream_type = ESDS_STREAM_TYPE_AUDIO; + else if (fourcc == FOURCC_mp4v) + stream_type = ESDS_STREAM_TYPE_VISUAL; + if (stream_type) + result = build_esds_atom (track_id, esds_type, stream_type, codec_data); + } else { + switch (fourcc) { + case FOURCC_mp4a: + if (esds_type != ESDS_OBJECT_TYPE_MPEG1_P3) { + result = build_mov_aac_extension (track_id, codec_data); + } + break; + case FOURCC_mp4v: + { + guint8 stream_type = ESDS_STREAM_TYPE_VISUAL; + + result = build_esds_atom (track_id, esds_type, stream_type, codec_data); + break; + } + } + } + + /* stable extension across format (or only relevant for particular format) */ + switch (fourcc) { + case FOURCC_avc1: + ext_fourcc = FOURCC_avcC; + break; + } + + /* simply wrap codec data in some atom */ + if (ext_fourcc && codec_data) { + AtomData *data; + + data = atom_data_new_from_gst_buffer (ext_fourcc, codec_data); + result = build_atom_info_wrapper ((Atom *) data, atom_data_copy_data, + atom_data_free); + } + + return result; +} diff --git a/gst/quicktime/atoms.h b/gst/quicktime/atoms.h new file mode 100644 index 000000000..f469abc1c --- /dev/null +++ b/gst/quicktime/atoms.h @@ -0,0 +1,620 @@ +/* Quicktime muxer plugin for GStreamer + * Copyright (C) 2008 Thiago Sousa Santos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ATOMS_H__ +#define __ATOMS_H__ + +#include +#include + +#include "descriptors.h" +#include "properties.h" +#include "fourcc.h" +#include "ftypcc.h" + +/* light-weight context that may influence header atom tree construction */ +typedef enum _AtomsTreeFlavor +{ + ATOMS_TREE_FLAVOR_MOV, + ATOMS_TREE_FLAVOR_ISOM +} AtomsTreeFlavor; + +typedef struct _AtomsContext +{ + AtomsTreeFlavor flavor; +} AtomsContext; + +AtomsContext* atoms_context_new (AtomsTreeFlavor flavor); +void atoms_context_free (AtomsContext *context); + +#define METADATA_DATA_FLAG 0x0 +#define METADATA_TEXT_FLAG 0x1 + +/* atom defs and functions */ + +/** + * Used for storing time related values for some atoms. + */ +typedef struct _TimeInfo +{ + guint64 creation_time; + guint64 modification_time; + guint32 timescale; + guint64 duration; +} TimeInfo; + +typedef struct _Atom +{ + guint32 size; + guint32 type; + guint64 extended_size; +} Atom; + +typedef struct _AtomFull +{ + Atom header; + + guint8 version; + guint8 flags[3]; +} AtomFull; + +/* + * Generic extension atom + */ +typedef struct _AtomData +{ + Atom header; + + /* not written */ + guint32 datalen; + guint8 *data; +} AtomData; + +typedef struct _AtomFTYP +{ + Atom header; + guint32 major_brand; + guint32 version; + guint32 *compatible_brands; + + /* not written */ + guint32 compatible_brands_size; +} AtomFTYP; + +typedef struct _AtomMVHD +{ + AtomFull header; + + /* version 0: 32 bits */ + TimeInfo time_info; + + guint32 prefered_rate; /* ISO: 0x00010000 */ + guint16 volume; /* ISO: 0x0100 */ + guint16 reserved3; /* ISO: 0x0 */ + guint32 reserved4[2]; /* ISO: 0, 0 */ + /* ISO: identity matrix = + * { 0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000 } */ + guint32 matrix[9]; + + /* ISO: all 0 */ + guint32 preview_time; + guint32 preview_duration; + guint32 poster_time; + guint32 selection_time; + guint32 selection_duration; + guint32 current_time; + + guint32 next_track_id; +} AtomMVHD; + +typedef struct _AtomTKHD +{ + AtomFull header; + + /* version 0: 32 bits */ + /* like the TimeInfo struct, but it has this track_ID inside */ + guint64 creation_time; + guint64 modification_time; + guint32 track_ID; + guint32 reserved; + guint64 duration; + + guint32 reserved2[2]; + guint16 layer; + guint16 alternate_group; + guint16 volume; + guint16 reserved3; + + /* ISO: identity matrix = + * { 0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000 } */ + guint32 matrix[9]; + guint32 width; + guint32 height; +} AtomTKHD; + +typedef struct _AtomMDHD +{ + AtomFull header; + + /* version 0: 32 bits */ + TimeInfo time_info; + + /* ISO: packed ISO-639-2/T language code (first bit must be 0) */ + guint16 language_code; + /* ISO: 0 */ + guint16 quality; +} AtomMDHD; + +typedef struct _AtomHDLR +{ + AtomFull header; + + /* ISO: 0 */ + guint32 component_type; + guint32 handler_type; + guint32 manufacturer; + guint32 flags; + guint32 flags_mask; + gchar *name; +} AtomHDLR; + +typedef struct _AtomVMHD +{ + AtomFull header; /* ISO: flags = 1 */ + + guint16 graphics_mode; + /* RGB */ + guint16 opcolor[3]; +} AtomVMHD; + +typedef struct _AtomSMHD +{ + AtomFull header; + + guint16 balance; + guint16 reserved; +} AtomSMHD; + +typedef struct _AtomHMHD +{ + AtomFull header; + + guint16 max_pdu_size; + guint16 avg_pdu_size; + guint32 max_bitrate; + guint32 avg_bitrate; + guint32 sliding_avg_bitrate; +} AtomHMHD; + +typedef struct _AtomURL +{ + AtomFull header; + + gchar *location; +} AtomURL; + +typedef struct _AtomDREF +{ + AtomFull header; + + GList *entries; +} AtomDREF; + +typedef struct _AtomDINF +{ + Atom header; + + AtomDREF dref; +} AtomDINF; + +typedef struct _STTSEntry +{ + guint32 sample_count; + gint32 sample_delta; +} STTSEntry; + +typedef struct _AtomSTTS +{ + AtomFull header; + + guint n_entries; + /* list of STTSEntry */ + GList *entries; +} AtomSTTS; + +typedef struct _AtomSTSS +{ + AtomFull header; + + guint n_entries; + /* list of sample indexes (guint32) */ + GList *entries; +} AtomSTSS; + +typedef struct _AtomESDS +{ + AtomFull header; + + ESDescriptor es; +} AtomESDS; + +typedef struct _AtomFRMA +{ + Atom header; + + guint32 media_type; +} AtomFRMA; + +typedef enum _SampleEntryKind +{ + UNKNOWN, + AUDIO, + VIDEO +} SampleEntryKind; + +typedef struct _SampleTableEntry +{ + Atom header; + + guint8 reserved[6]; + guint16 data_reference_index; + + /* sort of entry */ + SampleEntryKind kind; +} SampleTableEntry; + +typedef struct _AtomHintSampleEntry +{ + SampleTableEntry se; + guint32 size; + guint8 *data; +} AtomHintSampleEntry; + +typedef struct _SampleTableEntryMP4V +{ + SampleTableEntry se; + + guint16 version; + guint16 revision_level; + + guint32 vendor; /* fourcc code */ + guint32 temporal_quality; + guint32 spatial_quality; + + guint16 width; + guint16 height; + + guint32 horizontal_resolution; + guint32 vertical_resolution; + guint32 datasize; + + guint16 frame_count; /* usually 1 */ + + guint8 compressor[32]; /* pascal string, i.e. first byte = length */ + + guint16 depth; + guint16 color_table_id; + + /* (optional) list of AtomInfo */ + GList *extension_atoms; +} SampleTableEntryMP4V; + +typedef struct _SampleTableEntryMP4A +{ + SampleTableEntry se; + + guint16 version; + guint16 revision_level; + guint32 vendor; + + guint16 channels; + guint16 sample_size; + guint16 compression_id; + guint16 packet_size; + + guint32 sample_rate; /* fixed point 16.16 */ + + guint32 samples_per_packet; + guint32 bytes_per_packet; + guint32 bytes_per_frame; + guint32 bytes_per_sample; + + /* (optional) list of AtomInfo */ + GList *extension_atoms; +} SampleTableEntryMP4A; + +typedef struct _SampleTableEntryMP4S +{ + SampleTableEntry se; + + AtomESDS es; +} SampleTableEntryMP4S; + +typedef struct _AtomSTSD +{ + AtomFull header; + + guint n_entries; + /* list of subclasses of SampleTableEntry */ + GList *entries; +} AtomSTSD; + +typedef struct _AtomSTSZ +{ + AtomFull header; + + guint32 sample_size; + + /* need the size here because when sample_size is constant, + * the list is empty */ + guint32 table_size; + /* list of guint32 */ + GList *entries; +} AtomSTSZ; + +typedef struct _STSCEntry +{ + guint32 first_chunk; + guint32 samples_per_chunk; + guint32 sample_description_index; +} STSCEntry; + +typedef struct _AtomSTSC +{ + AtomFull header; + + guint n_entries; + /* list of STSCEntry */ + GList *entries; +} AtomSTSC; + + +/* + * used for both STCO and CO64 + * if used as STCO, entries should be truncated to use only 32bits + */ +typedef struct _AtomSTCO64 +{ + AtomFull header; + + guint n_entries; + /* list of guint64 */ + GList *entries; +} AtomSTCO64; + +typedef struct _CTTSEntry +{ + guint32 samplecount; + guint32 sampleoffset; +} CTTSEntry; + +typedef struct _AtomCTTS +{ + AtomFull header; + + /* also entry count here */ + guint n_entries; + GList *entries; +} AtomCTTS; + +typedef struct _AtomSTBL +{ + Atom header; + + AtomSTSD stsd; + AtomSTTS stts; + AtomSTSS stss; + AtomSTSC stsc; + AtomSTSZ stsz; + /* NULL if not present */ + AtomCTTS *ctts; + + AtomSTCO64 stco64; +} AtomSTBL; + +typedef struct _AtomMINF +{ + Atom header; + + /* only (exactly) one of those must be present */ + AtomVMHD *vmhd; + AtomSMHD *smhd; + AtomHMHD *hmhd; + + AtomHDLR *hdlr; + AtomDINF dinf; + AtomSTBL stbl; +} AtomMINF; + +typedef struct _AtomMDIA +{ + Atom header; + + AtomMDHD mdhd; + AtomHDLR hdlr; + AtomMINF minf; +} AtomMDIA; + +typedef struct _AtomILST +{ + Atom header; + + /* list of AtomInfo */ + GList* entries; +} AtomILST; + +typedef struct _AtomTagData +{ + AtomFull header; + guint32 reserved; + + guint32 datalen; + guint8* data; +} AtomTagData; + +typedef struct _AtomTag +{ + Atom header; + + AtomTagData data; +} AtomTag; + +typedef struct _AtomMETA +{ + AtomFull header; + AtomHDLR hdlr; + AtomILST *ilst; +} AtomMETA; + +typedef struct _AtomUDTA +{ + Atom header; + + AtomMETA *meta; +} AtomUDTA; + +typedef struct _AtomTRAK +{ + Atom header; + + AtomTKHD tkhd; + AtomMDIA mdia; +} AtomTRAK; + +typedef struct _AtomMOOV +{ + Atom header; + + AtomMVHD mvhd; + + /* list of AtomTRAK */ + GList *traks; + AtomUDTA *udta; +} AtomMOOV; + +typedef struct _AtomWAVE +{ + Atom header; + + /* list of AtomInfo */ + GList *extension_atoms; +} AtomWAVE; + + +/* + * Function to serialize an atom + */ +typedef guint64 (*AtomCopyDataFunc) (Atom *atom, guint8 **buffer, guint64 *size, guint64 *offset); + +/* + * Releases memory allocated by an atom + */ +typedef guint64 (*AtomFreeFunc) (Atom *atom); + +/* + * Some atoms might have many optional different kinds of child atoms, so this + * is useful for enabling generic handling of any atom. + * All we need are the two functions (copying it to an array + * for serialization and the memory releasing function). + */ +typedef struct _AtomInfo +{ + Atom *atom; + AtomCopyDataFunc copy_data_func; + AtomFreeFunc free_func; +} AtomInfo; + + +guint64 atom_copy_data (Atom *atom, guint8 **buffer, + guint64 *size, guint64* offset); + +AtomFTYP* atom_ftyp_new (AtomsContext *context, guint32 major, + guint32 version, GList *brands); +guint64 atom_ftyp_copy_data (AtomFTYP *ftyp, guint8 **buffer, + guint64 *size, guint64 *offset); +void atom_ftyp_free (AtomFTYP *ftyp); + +AtomTRAK* atom_trak_new (AtomsContext *context); +void atom_trak_add_samples (AtomTRAK * trak, guint32 nsamples, guint32 delta, + guint32 size, guint64 chunk_offset, gboolean sync, + gboolean do_pts, gint64 pts_offset); +guint32 atom_trak_get_timescale (AtomTRAK *trak); + +AtomMOOV* atom_moov_new (AtomsContext *context); +void atom_moov_free (AtomMOOV *moov); +guint64 atom_moov_copy_data (AtomMOOV *atom, guint8 **buffer, guint64 *size, guint64* offset); +void atom_moov_update_timescale (AtomMOOV *moov, guint32 timescale); +void atom_moov_update_duration (AtomMOOV *moov); +void atom_moov_set_64bits (AtomMOOV *moov, gboolean large_file); +void atom_moov_chunks_add_offset (AtomMOOV *moov, guint32 offset); +void atom_moov_add_trak (AtomMOOV *moov, AtomTRAK *trak); + +/* media sample description related helpers */ + +typedef struct +{ + guint32 fourcc; + guint width; + guint height; + guint depth; + guint frame_count; + gint color_table_id; + + GstBuffer *codec_data; +} VisualSampleEntry; + +typedef struct +{ + guint32 fourcc; + guint version; + gint compression_id; + guint sample_rate; + guint channels; + guint sample_size; + guint bytes_per_packet; + guint samples_per_packet; + guint bytes_per_sample; + guint bytes_per_frame; + + GstBuffer *codec_data; +} AudioSampleEntry; + +AtomInfo* build_sample_entry_extension (AtomTRAK * trak, AtomsTreeFlavor flavor, + guint32 fourcc, guint esds_type, + const GstBuffer * codec_data); + +void atom_trak_set_audio_type (AtomTRAK * trak, AtomsContext * context, + AudioSampleEntry * entry, guint32 scale, + AtomInfo * ext, gint sample_size); +void atom_trak_set_video_type (AtomTRAK * trak, AtomsContext * context, + VisualSampleEntry * entry, guint32 rate, + AtomInfo * ext); + + +/* + * Meta tags functions + */ +void atom_moov_add_str_tag (AtomMOOV *moov, guint32 fourcc, const gchar *value); +void atom_moov_add_uint_tag (AtomMOOV *moov, guint32 fourcc, guint32 flags, + guint32 value); +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); + +#endif /* __ATOMS_H__ */ diff --git a/gst/quicktime/descriptors.c b/gst/quicktime/descriptors.c new file mode 100644 index 000000000..edb6b1961 --- /dev/null +++ b/gst/quicktime/descriptors.c @@ -0,0 +1,442 @@ +/* Quicktime muxer plugin for GStreamer + * Copyright (C) 2008 Thiago Sousa Santos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "descriptors.h" + +/** + * Some mp4 structures (descriptors) use a coding scheme for + * representing its size. + * It is grouped in bytes. The 1st bit set to 1 means we need another byte, + * 0 otherwise. The remaining 7 bits are the useful values. + * + * The next set of functions handle those values + */ + +/** + * Gets an unsigned integer and packs it into a 'expandable size' format + * (as used by mp4 descriptors) + * @size: the integer to be parsed + * @ptr: the array to place the result + * @array_size: the size of ptr array + */ +static void +expandable_size_parse (guint64 size, guint8 * ptr, guint32 array_size) +{ + int index = 0; + + memset (ptr, 0, sizeof (array_size)); + while (size > 0 && index < array_size) { + ptr[index++] = (size > 0x7F ? 0x80 : 0x0) | (size & 0x7F); + size = size >> 7; + } +} + +/** + * Gets how many positions in an array holding an 'expandable size' + * are really used + * + * @ptr: the array with the 'expandable size' + * @array_size: the size of ptr array + * + * Returns: the number of really used positions + */ +static guint64 +expandable_size_get_length (guint8 * ptr, guint32 array_size) +{ + gboolean next = TRUE; + guint32 index = 0; + + while (next && index < array_size) { + next = ((ptr[index] & 0x80) == 1); + index++; + } + return index; +} + +/* + * Initializers below + */ + +static void +desc_base_descriptor_init (BaseDescriptor * bd, guint8 tag, guint32 size) +{ + bd->tag = tag; + expandable_size_parse (size, bd->size, 4); +} + +static void +desc_dec_specific_info_init (DecoderSpecificInfoDescriptor * dsid) +{ + desc_base_descriptor_init (&dsid->base, DECODER_SPECIFIC_INFO_TAG, 0); + dsid->length = 0; + dsid->data = NULL; +} + +DecoderSpecificInfoDescriptor * +desc_dec_specific_info_new () +{ + DecoderSpecificInfoDescriptor *desc = + g_new0 (DecoderSpecificInfoDescriptor, 1); + desc_dec_specific_info_init (desc); + return desc; +} + +static void +desc_dec_conf_desc_init (DecoderConfigDescriptor * dcd) +{ + desc_base_descriptor_init (&dcd->base, DECODER_CONFIG_DESC_TAG, 0); + dcd->dec_specific_info = NULL; +} + +static void +desc_sl_conf_desc_init (SLConfigDescriptor * sl) +{ + desc_base_descriptor_init (&sl->base, SL_CONFIG_DESC_TAG, 0); + sl->predefined = 0x2; +} + +void +desc_es_init (ESDescriptor * es) +{ + desc_base_descriptor_init (&es->base, ES_DESCRIPTOR_TAG, 0); + + es->id = 0; + es->flags = 0; + es->depends_on_es_id = 0; + es->ocr_es_id = 0; + es->url_length = 0; + es->url_string = NULL; + + desc_dec_conf_desc_init (&es->dec_conf_desc); + desc_sl_conf_desc_init (&es->sl_conf_desc); +} + +ESDescriptor * +desc_es_descriptor_new () +{ + ESDescriptor *es = g_new0 (ESDescriptor, 1); + + desc_es_init (es); + return es; +} + +/* + * Deinitializers/Destructors below + */ + +static void +desc_base_descriptor_clear (BaseDescriptor * base) +{ +} + +void +desc_dec_specific_info_free (DecoderSpecificInfoDescriptor * dsid) +{ + desc_base_descriptor_clear (&dsid->base); + if (dsid->data) { + g_free (dsid->data); + dsid->data = NULL; + } + g_free (dsid); +} + +static void +desc_dec_conf_desc_clear (DecoderConfigDescriptor * dec) +{ + desc_base_descriptor_clear (&dec->base); + if (dec->dec_specific_info) { + desc_dec_specific_info_free (dec->dec_specific_info); + } +} + +static void +desc_sl_config_descriptor_clear (SLConfigDescriptor * sl) +{ + desc_base_descriptor_clear (&sl->base); +} + +void +desc_es_descriptor_clear (ESDescriptor * es) +{ + desc_base_descriptor_clear (&es->base); + if (es->url_string) { + g_free (es->url_string); + es->url_string = NULL; + } + desc_dec_conf_desc_clear (&es->dec_conf_desc); + desc_sl_config_descriptor_clear (&es->sl_conf_desc); +} + +void +desc_es_descriptor_free (ESDescriptor * es) +{ + desc_es_descriptor_clear (es); + g_free (es); +} + +/* + * Size handling functions below + */ + +void +desc_dec_specific_info_alloc_data (DecoderSpecificInfoDescriptor * dsid, + guint32 size) +{ + if (dsid->data) { + g_free (dsid->data); + } + dsid->data = g_new0 (guint8, size); + dsid->length = size; +} + +static void +desc_base_descriptor_set_size (BaseDescriptor * bd, guint32 size) +{ + expandable_size_parse (size, bd->size, 4); +} + +static guint64 +desc_base_descriptor_get_size (BaseDescriptor * bd) +{ + guint64 size = 0; + + size += sizeof (guint8); + size += expandable_size_get_length (bd->size, 4) * sizeof (guint8); + return size; +} + +static guint64 +desc_sl_config_descriptor_get_size (SLConfigDescriptor * sl_desc) +{ + guint64 size = 0; + guint64 extra_size = 0; + + size += desc_base_descriptor_get_size (&sl_desc->base); + /* predefined */ + extra_size += sizeof (guint8); + + desc_base_descriptor_set_size (&sl_desc->base, extra_size); + + return size + extra_size; +} + +static guint64 +desc_dec_specific_info_get_size (DecoderSpecificInfoDescriptor * dsid) +{ + guint64 size = 0; + guint64 extra_size = 0; + + size += desc_base_descriptor_get_size (&dsid->base); + extra_size += sizeof (guint8) * dsid->length; + desc_base_descriptor_set_size (&dsid->base, extra_size); + return size + extra_size; +} + +static guint64 +desc_dec_config_descriptor_get_size (DecoderConfigDescriptor * dec_desc) +{ + guint64 size = 0; + guint64 extra_size = 0; + + size += desc_base_descriptor_get_size (&dec_desc->base); + /* object type */ + extra_size += sizeof (guint8); + /* stream type */ + extra_size += sizeof (guint8); + /* buffer size */ + extra_size += sizeof (guint8) * 3; + /* max bitrate */ + extra_size += sizeof (guint32); + /* avg bitrate */ + extra_size += sizeof (guint32); + if (dec_desc->dec_specific_info) { + extra_size += desc_dec_specific_info_get_size (dec_desc->dec_specific_info); + } + + desc_base_descriptor_set_size (&dec_desc->base, extra_size); + return size + extra_size; +} + +static guint64 +desc_es_descriptor_get_size (ESDescriptor * es) +{ + guint64 size = 0; + guint64 extra_size = 0; + + size += desc_base_descriptor_get_size (&es->base); + /* id */ + extra_size += sizeof (guint16); + /* flags */ + extra_size += sizeof (guint8); + /* depends_on_es_id */ + if (es->flags & 0x80) { + extra_size += sizeof (guint16); + } + if (es->flags & 0x40) { + /* url_length */ + extra_size += sizeof (guint8); + /* url */ + extra_size += sizeof (gchar) * es->url_length; + } + if (es->flags & 0x20) { + /* ocr_es_id */ + extra_size += sizeof (guint16); + } + + extra_size += desc_dec_config_descriptor_get_size (&es->dec_conf_desc); + extra_size += desc_sl_config_descriptor_get_size (&es->sl_conf_desc); + + desc_base_descriptor_set_size (&es->base, extra_size); + + return size + extra_size; +} + +static gboolean +desc_es_descriptor_check_stream_dependency (ESDescriptor * es) +{ + return es->flags & 0x80; +} + +static gboolean +desc_es_descriptor_check_url_flag (ESDescriptor * es) +{ + return es->flags & 0x40; +} + +static gboolean +desc_es_descriptor_check_ocr (ESDescriptor * es) +{ + return es->flags & 0x20; +} + +/* Copy/Serializations Functions below */ + +static guint64 +desc_base_descriptor_copy_data (BaseDescriptor * desc, guint8 ** buffer, + guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + + prop_copy_uint8 (desc->tag, buffer, size, offset); + prop_copy_uint8_array (desc->size, expandable_size_get_length (desc->size, 4), + buffer, size, offset); + return original_offset - *offset; +} + +static guint64 +desc_sl_config_descriptor_copy_data (SLConfigDescriptor * desc, + guint8 ** buffer, guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!desc_base_descriptor_copy_data (&desc->base, buffer, size, offset)) { + return 0; + } + /* predefined attribute */ + prop_copy_uint8 (desc->predefined, buffer, size, offset); + + return *offset - original_offset; +} + +static guint64 +desc_dec_specific_info_copy_data (DecoderSpecificInfoDescriptor * desc, + guint8 ** buffer, guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!desc_base_descriptor_copy_data (&desc->base, buffer, size, offset)) { + return 0; + } + prop_copy_uint8_array (desc->data, desc->length, buffer, size, offset); + + return *offset - original_offset; +} + +static guint64 +desc_dec_config_descriptor_copy_data (DecoderConfigDescriptor * desc, + guint8 ** buffer, guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!desc_base_descriptor_copy_data (&desc->base, buffer, size, offset)) { + return 0; + } + + prop_copy_uint8 (desc->object_type, buffer, size, offset); + + prop_copy_uint8 (desc->stream_type, buffer, size, offset); + prop_copy_uint8_array (desc->buffer_size_DB, 3, buffer, size, offset); + + prop_copy_uint32 (desc->max_bitrate, buffer, size, offset); + prop_copy_uint32 (desc->avg_bitrate, buffer, size, offset); + + if (desc->dec_specific_info) { + if (!desc_dec_specific_info_copy_data (desc->dec_specific_info, buffer, + size, offset)) { + return 0; + } + } + + return *offset - original_offset; +} + +guint64 +desc_es_descriptor_copy_data (ESDescriptor * desc, guint8 ** buffer, + guint64 * size, guint64 * offset) +{ + guint64 desc_size; + guint64 original_offset = *offset; + + /* must call this twice to have size fields of all contained descriptors set + * correctly, and to have the size of the size fields taken into account */ + desc_size = desc_es_descriptor_get_size (desc); + desc_size = desc_es_descriptor_get_size (desc); + + if (!desc_base_descriptor_copy_data (&desc->base, buffer, size, offset)) { + return 0; + } + /* id and flags */ + prop_copy_uint16 (desc->id, buffer, size, offset); + prop_copy_uint8 (desc->flags, buffer, size, offset); + + if (desc_es_descriptor_check_stream_dependency (desc)) { + prop_copy_uint16 (desc->depends_on_es_id, buffer, size, offset); + } + + if (desc_es_descriptor_check_url_flag (desc)) { + prop_copy_size_string (desc->url_string, desc->url_length, buffer, size, + offset); + } + + if (desc_es_descriptor_check_ocr (desc)) { + prop_copy_uint16 (desc->ocr_es_id, buffer, size, offset); + } + + if (!desc_dec_config_descriptor_copy_data (&desc->dec_conf_desc, buffer, size, + offset)) { + return 0; + } + + if (!desc_sl_config_descriptor_copy_data (&desc->sl_conf_desc, buffer, size, + offset)) { + return 0; + } + + return *offset - original_offset; +} diff --git a/gst/quicktime/descriptors.h b/gst/quicktime/descriptors.h new file mode 100644 index 000000000..db05a2ba3 --- /dev/null +++ b/gst/quicktime/descriptors.h @@ -0,0 +1,128 @@ +/* Quicktime muxer plugin for GStreamer + * Copyright (C) 2008 Thiago Sousa Santos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __DESCRIPTORS_H__ +#define __DESCRIPTORS_H__ + +#include +#include +#include "properties.h" + +/* + * Tags for descriptor (each kind is represented by a number, instead of fourcc as in atoms) + */ +#define OBJECT_DESC_TAG 0x01 +#define INIT_OBJECT_DESC_TAG 0x02 +#define ES_DESCRIPTOR_TAG 0x03 +#define DECODER_CONFIG_DESC_TAG 0x04 +#define DECODER_SPECIFIC_INFO_TAG 0x05 +#define SL_CONFIG_DESC_TAG 0x06 +#define ES_ID_INC_TAG 0x0E +#define MP4_INIT_OBJECT_DESC_TAG 0x10 + +#define ESDS_OBJECT_TYPE_MPEG1_P3 0x6B +#define ESDS_OBJECT_TYPE_MPEG2_P7_MAIN 0x66 +#define ESDS_OBJECT_TYPE_MPEG4_P7_LC 0x67 +#define ESDS_OBJECT_TYPE_MPEG4_P7_SSR 0x68 +#define ESDS_OBJECT_TYPE_MPEG4_P2 0x20 +#define ESDS_OBJECT_TYPE_MPEG4_P3 0x40 + +#define ESDS_STREAM_TYPE_VISUAL 0x04 +#define ESDS_STREAM_TYPE_AUDIO 0x05 + + +typedef struct _BaseDescriptor +{ + guint8 tag; + /* the first bit of each byte indicates if the next byte should be used */ + guint8 size[4]; +} BaseDescriptor; + +typedef struct _SLConfigDescriptor +{ + BaseDescriptor base; + + guint8 predefined; /* everything is supposed predefined */ +} SLConfigDescriptor; + +typedef struct _DecoderSpecificInfoDescriptor +{ + BaseDescriptor base; + guint32 length; + guint8 *data; +} DecoderSpecificInfoDescriptor; + +typedef struct _DecoderConfigDescriptor { + BaseDescriptor base; + + guint8 object_type; + + /* following are condensed into streamType: + * bit(6) streamType; + * bit(1) upStream; + * const bit(1) reserved=1; + */ + guint8 stream_type; + + guint8 buffer_size_DB[3]; + guint32 max_bitrate; + guint32 avg_bitrate; + + DecoderSpecificInfoDescriptor *dec_specific_info; +} DecoderConfigDescriptor; + +typedef struct _ESDescriptor +{ + BaseDescriptor base; + + guint16 id; + + /* flags contains the following: + * bit(1) streamDependenceFlag; + * bit(1) URL_Flag; + * bit(1) OCRstreamFlag; + * bit(5) streamPriority; + */ + guint8 flags; + + guint16 depends_on_es_id; + guint8 url_length; /* only if URL_flag is set */ + guint8 *url_string; /* size is url_length */ + + guint16 ocr_es_id; /* only if OCRstreamFlag is set */ + + DecoderConfigDescriptor dec_conf_desc; + SLConfigDescriptor sl_conf_desc; + + /* optional remainder of ESDescriptor is not used */ +} ESDescriptor; + +/* --- FUNCTIONS --- */ +void desc_es_init (ESDescriptor *es); +ESDescriptor *desc_es_descriptor_new (); +guint64 desc_es_descriptor_copy_data (ESDescriptor *es, guint8 **buffer, + guint64 *size, guint64 *offset); +void desc_es_descriptor_clear (ESDescriptor *es); + +DecoderSpecificInfoDescriptor *desc_dec_specific_info_new(); +void desc_dec_specific_info_free (DecoderSpecificInfoDescriptor *dsid); +void desc_dec_specific_info_alloc_data (DecoderSpecificInfoDescriptor *dsid, + guint32 size); + +#endif /* __DESCRIPTORS_H__ */ diff --git a/gst/quicktime/fourcc.h b/gst/quicktime/fourcc.h new file mode 100644 index 000000000..905ca2317 --- /dev/null +++ b/gst/quicktime/fourcc.h @@ -0,0 +1,159 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __FOURCC_H__ +#define __FOURCC_H__ + +#include + +G_BEGIN_DECLS + +#define FOURCC_null 0x0 + +#define FOURCC_moov GST_MAKE_FOURCC('m','o','o','v') +#define FOURCC_mvhd GST_MAKE_FOURCC('m','v','h','d') +#define FOURCC_clip GST_MAKE_FOURCC('c','l','i','p') +#define FOURCC_trak GST_MAKE_FOURCC('t','r','a','k') +#define FOURCC_udta GST_MAKE_FOURCC('u','d','t','a') +#define FOURCC_ctab GST_MAKE_FOURCC('c','t','a','b') +#define FOURCC_tkhd GST_MAKE_FOURCC('t','k','h','d') +#define FOURCC_crgn GST_MAKE_FOURCC('c','r','g','n') +#define FOURCC_matt GST_MAKE_FOURCC('m','a','t','t') +#define FOURCC_kmat GST_MAKE_FOURCC('k','m','a','t') +#define FOURCC_edts GST_MAKE_FOURCC('e','d','t','s') +#define FOURCC_elst GST_MAKE_FOURCC('e','l','s','t') +#define FOURCC_load GST_MAKE_FOURCC('l','o','a','d') +#define FOURCC_tref GST_MAKE_FOURCC('t','r','e','f') +#define FOURCC_imap GST_MAKE_FOURCC('i','m','a','p') +#define FOURCC___in GST_MAKE_FOURCC(' ',' ','i','n') +#define FOURCC___ty GST_MAKE_FOURCC(' ',' ','t','y') +#define FOURCC_mdia GST_MAKE_FOURCC('m','d','i','a') +#define FOURCC_mdhd GST_MAKE_FOURCC('m','d','h','d') +#define FOURCC_hdlr GST_MAKE_FOURCC('h','d','l','r') +#define FOURCC_dhlr GST_MAKE_FOURCC('d','h','l','r') +#define FOURCC_mhlr GST_MAKE_FOURCC('m','h','l','r') +#define FOURCC_minf GST_MAKE_FOURCC('m','i','n','f') +#define FOURCC_mdir GST_MAKE_FOURCC('m','d','i','r') +#define FOURCC_vmhd GST_MAKE_FOURCC('v','m','h','d') +#define FOURCC_smhd GST_MAKE_FOURCC('s','m','h','d') +#define FOURCC_gmhd GST_MAKE_FOURCC('g','m','h','d') +#define FOURCC_gmin GST_MAKE_FOURCC('g','m','i','n') +#define FOURCC_dinf GST_MAKE_FOURCC('d','i','n','f') +#define FOURCC_dref GST_MAKE_FOURCC('d','r','e','f') +#define FOURCC_stbl GST_MAKE_FOURCC('s','t','b','l') +#define FOURCC_stsd GST_MAKE_FOURCC('s','t','s','d') +#define FOURCC_stts GST_MAKE_FOURCC('s','t','t','s') +#define FOURCC_stss GST_MAKE_FOURCC('s','t','s','s') +#define FOURCC_stsc GST_MAKE_FOURCC('s','t','s','c') +#define FOURCC_stsz GST_MAKE_FOURCC('s','t','s','z') +#define FOURCC_stco GST_MAKE_FOURCC('s','t','c','o') +#define FOURCC_vide GST_MAKE_FOURCC('v','i','d','e') +#define FOURCC_soun GST_MAKE_FOURCC('s','o','u','n') +#define FOURCC_strm GST_MAKE_FOURCC('s','t','r','m') +#define FOURCC_rtsp GST_MAKE_FOURCC('r','t','s','p') +#define FOURCC_co64 GST_MAKE_FOURCC('c','o','6','4') +#define FOURCC_cmov GST_MAKE_FOURCC('c','m','o','v') +#define FOURCC_dcom GST_MAKE_FOURCC('d','c','o','m') +#define FOURCC_cmvd GST_MAKE_FOURCC('c','m','v','d') +#define FOURCC_hint GST_MAKE_FOURCC('h','i','n','t') +#define FOURCC_mp4a GST_MAKE_FOURCC('m','p','4','a') +#define FOURCC__mp3 GST_MAKE_FOURCC('.','m','p','3') +#define FOURCC_mp4s GST_MAKE_FOURCC('m','p','4','s') +#define FOURCC_mp4v GST_MAKE_FOURCC('m','p','4','v') +#define FOURCC_2vuy GST_MAKE_FOURCC('2','v','u','y') +#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_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',' ') +#define FOURCC_meta GST_MAKE_FOURCC('m','e','t','a') +#define FOURCC_ilst GST_MAKE_FOURCC('i','l','s','t') +#define FOURCC__nam GST_MAKE_FOURCC(0xa9,'n','a','m') +#define FOURCC__ART GST_MAKE_FOURCC(0xa9,'A','R','T') +#define FOURCC__wrt GST_MAKE_FOURCC(0xa9,'w','r','t') +#define FOURCC__grp GST_MAKE_FOURCC(0xa9,'g','r','p') +#define FOURCC__alb GST_MAKE_FOURCC(0xa9,'a','l','b') +#define FOURCC__day GST_MAKE_FOURCC(0xa9,'d','a','y') +#define FOURCC__des GST_MAKE_FOURCC(0xa9,'d','e','s') +#define FOURCC_gnre GST_MAKE_FOURCC('g','n','r','e') +#define FOURCC_disc GST_MAKE_FOURCC('d','i','s','c') +#define FOURCC_disk GST_MAKE_FOURCC('d','i','s','k') +#define FOURCC_trkn GST_MAKE_FOURCC('t','r','k','n') +#define FOURCC_cprt GST_MAKE_FOURCC('c','p','r','t') +#define FOURCC_covr GST_MAKE_FOURCC('c','o','v','r') +#define FOURCC_cpil GST_MAKE_FOURCC('c','p','i','l') +#define FOURCC_tmpo GST_MAKE_FOURCC('t','m','p','o') +#define FOURCC__too GST_MAKE_FOURCC(0xa9,'t','o','o') +#define FOURCC_keyw GST_MAKE_FOURCC('k','e','y','w') +#define FOURCC_____ GST_MAKE_FOURCC('-','-','-','-') +#define FOURCC_free GST_MAKE_FOURCC('f','r','e','e') +#define FOURCC_data GST_MAKE_FOURCC('d','a','t','a') +#define FOURCC_SVQ3 GST_MAKE_FOURCC('S','V','Q','3') +#define FOURCC_rmra GST_MAKE_FOURCC('r','m','r','a') +#define FOURCC_rmda GST_MAKE_FOURCC('r','m','d','a') +#define FOURCC_rdrf GST_MAKE_FOURCC('r','d','r','f') +#define FOURCC__gen GST_MAKE_FOURCC(0xa9, 'g', 'e', 'n') +#define FOURCC_rmdr GST_MAKE_FOURCC('r','m','d','r') +#define FOURCC_rmvc GST_MAKE_FOURCC('r','m','v','c') +#define FOURCC_qtim GST_MAKE_FOURCC('q','t','i','m') +#define FOURCC_drms GST_MAKE_FOURCC('d','r','m','s') +#define FOURCC_avc1 GST_MAKE_FOURCC('a','v','c','1') +#define FOURCC_h263 GST_MAKE_FOURCC('h','2','6','3') +#define FOURCC_avcC GST_MAKE_FOURCC('a','v','c','C') +#define FOURCC_VP31 GST_MAKE_FOURCC('V','P','3','1') +#define FOURCC_rle_ GST_MAKE_FOURCC('r','l','e',' ') +#define FOURCC_MAC6 GST_MAKE_FOURCC('M','A','C','6') +#define FOURCC_MAC3 GST_MAKE_FOURCC('M','A','C','3') +#define FOURCC_ima4 GST_MAKE_FOURCC('i','m','a','4') +#define FOURCC_ulaw GST_MAKE_FOURCC('u','l','a','w') +#define FOURCC_alaw GST_MAKE_FOURCC('a','l','a','w') +#define FOURCC_twos GST_MAKE_FOURCC('t','w','o','s') +#define FOURCC_sowt GST_MAKE_FOURCC('s','o','w','t') +#define FOURCC_raw_ GST_MAKE_FOURCC('r','a','w',' ') +#define FOURCC_QDM2 GST_MAKE_FOURCC('Q','D','M','2') +#define FOURCC_alac GST_MAKE_FOURCC('a','l','a','c') +#define FOURCC_samr GST_MAKE_FOURCC('s','a','m','r') +#define FOURCC_sawb GST_MAKE_FOURCC('s','a','w','b') +#define FOURCC_mdat GST_MAKE_FOURCC('m','d','a','t') +#define FOURCC_wide GST_MAKE_FOURCC('w','i','d','e') +#define FOURCC_PICT GST_MAKE_FOURCC('P','I','C','T') +#define FOURCC_pnot GST_MAKE_FOURCC('p','n','o','t') +#define FOURCC_zlib GST_MAKE_FOURCC('z','l','i','b') +#define FOURCC_alis GST_MAKE_FOURCC('a','l','i','s') +#define FOURCC_url_ GST_MAKE_FOURCC('u','r','l',' ') +#define FOURCC_frma GST_MAKE_FOURCC('f','r','m','a') +#define FOURCC_ctts GST_MAKE_FOURCC('c','t','t','s') +#define FOURCC_drac GST_MAKE_FOURCC('d','r','a','c') +#define FOURCC_jpeg GST_MAKE_FOURCC('j','p','e','g') + +/* Xiph fourcc */ +#define FOURCC_XiTh GST_MAKE_FOURCC('X','i','T','h') +#define FOURCC_XdxT GST_MAKE_FOURCC('X','d','x','T') +#define FOURCC_tCtH GST_MAKE_FOURCC('t','C','t','H') +#define FOURCC_tCt_ GST_MAKE_FOURCC('t','C','t','#') +#define FOURCC_tCtC GST_MAKE_FOURCC('t','C','t','C') + +/* ilst metatags */ +#define FOURCC_titl GST_MAKE_FOURCC('t','i','t','l') +#define FOURCC__cmt GST_MAKE_FOURCC(0xa9, 'c','m','t') + +G_END_DECLS + +#endif /* __FOURCC_H__ */ diff --git a/gst/quicktime/ftypcc.h b/gst/quicktime/ftypcc.h new file mode 100644 index 000000000..b00ebd1ca --- /dev/null +++ b/gst/quicktime/ftypcc.h @@ -0,0 +1,39 @@ +/* GStreamer + * Copyright (C) <2008> Thiago Sousa Santos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __FTYP_CC_H__ +#define __FTYP_CC_H__ + +#include + +G_BEGIN_DECLS + +#define FOURCC_ftyp GST_MAKE_FOURCC('f','t','y','p') +#define FOURCC_isom GST_MAKE_FOURCC('i','s','o','m') +#define FOURCC_iso2 GST_MAKE_FOURCC('i','s','o','2') +#define FOURCC_mp41 GST_MAKE_FOURCC('m','p','4','1') +#define FOURCC_mp42 GST_MAKE_FOURCC('m','p','4','2') +#define FOURCC_mjp2 GST_MAKE_FOURCC('m','j','p','2') +#define FOURCC_3gg7 GST_MAKE_FOURCC('3','g','g','7') +#define FOURCC_avc1 GST_MAKE_FOURCC('a','v','c','1') +#define FOURCC_qt__ GST_MAKE_FOURCC('q','t',' ',' ') + +G_END_DECLS + +#endif /* __FTYP_CC_H__ */ diff --git a/gst/quicktime/gstqtmux.c b/gst/quicktime/gstqtmux.c new file mode 100644 index 000000000..9a181aa92 --- /dev/null +++ b/gst/quicktime/gstqtmux.c @@ -0,0 +1,1823 @@ +/* Quicktime muxer plugin for GStreamer + * Copyright (C) 2008 Thiago Sousa Santos + * Copyright (C) 2008 Mark Nauwelaerts + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +/** + * SECTION:gstqtmux + * @short_description: Muxer for quicktime(.mov) files + * + * + * + * This element merges streams (audio and video) into qt(.mov) files. + * + * Example pipelines + * + * + * gst-launch v4l2src num-buffers=500 ! video/x-raw-yuv,width=320,height=240 ! ffmpegcolorspace ! qtmux ! filesink location=video.mov + * + * Records a video stream captured from a v4l2 device and muxes it into a qt file. + * + * + * + * Last reviewed on 2008-08-27 + */ + +/* + * Based on avimux + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include + +#include "gstqtmux.h" + +GST_DEBUG_CATEGORY_STATIC (gst_qt_mux_debug); +#define GST_CAT_DEFAULT gst_qt_mux_debug + +/* QTMux signals and args */ +enum +{ + /* FILL ME */ + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_LARGE_FILE, + PROP_MOVIE_TIMESCALE, + PROP_DO_CTTS, + PROP_FLAVOR, + PROP_FAST_START, + PROP_FAST_START_TEMP_FILE +}; + +#define MDAT_ATOM_HEADER_SIZE 16 +#define DEFAULT_LARGE_FILE FALSE +#define DEFAULT_MOVIE_TIMESCALE 600 +#define DEFAULT_DO_CTTS FALSE +#define DEFAULT_FAST_START FALSE +#define DEFAULT_FAST_START_TEMP_FILE NULL + +static void gst_qt_mux_finalize (GObject * object); + +static GstStateChangeReturn gst_qt_mux_change_state (GstElement * element, + GstStateChange transition); + +/* property functions */ +static void gst_qt_mux_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_qt_mux_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); + +/* pad functions */ +static GstPad *gst_qt_mux_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * name); +static void gst_qt_mux_release_pad (GstElement * element, GstPad * pad); + +/* event */ +static gboolean gst_qt_mux_sink_event (GstPad * pad, GstEvent * event); + +static GstFlowReturn gst_qt_mux_collected (GstCollectPads * pads, + gpointer user_data); +static GstFlowReturn gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, + GstBuffer * buf); + +static GstElementClass *parent_class = NULL; + +static void +gst_qt_mux_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + GstQTMuxClass *klass = (GstQTMuxClass *) g_class; + GstQTMuxClassParams *params; + GstElementDetails details; + GstPadTemplate *videosinktempl, *audiosinktempl, *srctempl; + + params = + (GstQTMuxClassParams *) g_type_get_qdata (G_OBJECT_CLASS_TYPE (g_class), + GST_QT_MUX_PARAMS_QDATA); + g_assert (params != NULL); + + /* construct the element details struct */ + details.longname = g_strdup_printf ("%s Muxer", params->prop->long_name); + details.klass = g_strdup ("Codec/Muxer"); + details.description = + g_strdup_printf ("Multiplex audio and video into a %s file", + params->prop->long_name); + details.author = "Thiago Sousa Santos "; + gst_element_class_set_details (element_class, &details); + g_free (details.longname); + g_free (details.klass); + g_free (details.description); + + /* pad templates */ + srctempl = gst_pad_template_new ("src", GST_PAD_SRC, + GST_PAD_ALWAYS, params->src_caps); + gst_element_class_add_pad_template (element_class, srctempl); + + if (params->audio_sink_caps) { + audiosinktempl = gst_pad_template_new ("audio_%d", + GST_PAD_SINK, GST_PAD_REQUEST, params->audio_sink_caps); + gst_element_class_add_pad_template (element_class, audiosinktempl); + } + + if (params->video_sink_caps) { + videosinktempl = gst_pad_template_new ("video_%d", + GST_PAD_SINK, GST_PAD_REQUEST, params->video_sink_caps); + gst_element_class_add_pad_template (element_class, videosinktempl); + } + + klass->format = params->prop->format; +} + +static void +gst_qt_mux_class_init (GstQTMuxClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + parent_class = g_type_class_peek_parent (klass); + + gobject_class->finalize = gst_qt_mux_finalize; + gobject_class->get_property = gst_qt_mux_get_property; + gobject_class->set_property = gst_qt_mux_set_property; + + g_object_class_install_property (gobject_class, PROP_LARGE_FILE, + g_param_spec_boolean ("large-file", "Support for large files", + "Uses 64bits to some fields instead of 32bits, " + "providing support for large files", + DEFAULT_LARGE_FILE, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, PROP_MOVIE_TIMESCALE, + g_param_spec_uint ("movie-timescale", "Movie timescale", + "Timescale to use in the movie (units per second)", + 1, G_MAXUINT32, DEFAULT_MOVIE_TIMESCALE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, PROP_DO_CTTS, + g_param_spec_boolean ("presentation-time", + "Include presentation-time info", + "Calculate and include presentation/composition time (in addition to decoding time)" + " (use with caution)", DEFAULT_DO_CTTS, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, PROP_FAST_START, + g_param_spec_boolean ("faststart", "Format file to faststart", + "If the file should be formated for faststart (headers first). ", + DEFAULT_FAST_START, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, PROP_FAST_START_TEMP_FILE, + g_param_spec_string ("faststart-file", "File to use for storing buffers", + "File that will be used temporarily to store data from the stream when " + "creating a faststart file. If null a filepath will be created automatically", + DEFAULT_FAST_START_TEMP_FILE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + gstelement_class->request_new_pad = + GST_DEBUG_FUNCPTR (gst_qt_mux_request_new_pad); + gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_qt_mux_change_state); + gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_qt_mux_release_pad); +} + +static void +gst_qt_mux_pad_reset (GstQTPad * qtpad) +{ + qtpad->fourcc = 0; + qtpad->is_out_of_order = FALSE; + qtpad->sample_size = 0; + qtpad->sync = FALSE; + qtpad->last_dts = 0; + + if (qtpad->last_buf) + gst_buffer_replace (&qtpad->last_buf, NULL); + + /* reference owned elsewhere */ + qtpad->trak = NULL; +} + +/* + * Takes GstQTMux back to its initial state + */ +static void +gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc) +{ + GSList *walk; + + qtmux->state = GST_QT_MUX_STATE_NONE; + qtmux->header_size = 0; + qtmux->mdat_size = 0; + qtmux->mdat_pos = 0; + + if (qtmux->ftyp) { + atom_ftyp_free (qtmux->ftyp); + qtmux->ftyp = NULL; + } + if (qtmux->moov) { + atom_moov_free (qtmux->moov); + qtmux->moov = NULL; + } + if (qtmux->tags) { + gst_tag_list_free (qtmux->tags); + qtmux->tags = NULL; + } + if (qtmux->fast_start_file) { + fclose (qtmux->fast_start_file); + qtmux->fast_start_file = NULL; + } + + /* reset pad data */ + for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) { + GstQTPad *qtpad = (GstQTPad *) walk->data; + gst_qt_mux_pad_reset (qtpad); + + /* hm, moov_free above yanked the traks away from us, + * so do not free, but do clear */ + qtpad->trak = NULL; + } + + if (alloc) { + qtmux->moov = atom_moov_new (qtmux->context); + } +} + +static void +gst_qt_mux_init (GstQTMux * qtmux, GstQTMuxClass * qtmux_klass) +{ + GstElementClass *klass = GST_ELEMENT_CLASS (qtmux_klass); + GstPadTemplate *templ; + GstCaps *caps; + + templ = gst_element_class_get_pad_template (klass, "src"); + qtmux->srcpad = gst_pad_new_from_template (templ, "src"); + caps = gst_caps_copy (gst_pad_get_pad_template_caps (qtmux->srcpad)); + gst_pad_set_caps (qtmux->srcpad, caps); + gst_caps_unref (caps); + gst_pad_use_fixed_caps (qtmux->srcpad); + gst_element_add_pad (GST_ELEMENT (qtmux), qtmux->srcpad); + + qtmux->collect = gst_collect_pads_new (); + gst_collect_pads_set_function (qtmux->collect, + (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_qt_mux_collected), qtmux); + + /* properties set to default upon construction */ + + /* always need this */ + qtmux->context = + atoms_context_new (gst_qt_mux_map_format_to_flavor (qtmux_klass->format)); + + /* internals to initial state */ + gst_qt_mux_reset (qtmux, TRUE); +} + + +static void +gst_qt_mux_finalize (GObject * object) +{ + GstQTMux *qtmux = GST_QT_MUX_CAST (object); + + gst_qt_mux_reset (qtmux, FALSE); + + if (qtmux->fast_start_file_path) + g_free (qtmux->fast_start_file_path); + + atoms_context_free (qtmux->context); + gst_object_unref (qtmux->collect); + + 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 */ + +/* + * Struct to record mappings from gstreamer tags to fourcc codes + */ +typedef struct _GstTagToFourcc +{ + guint32 fourcc; + const gchar *gsttag; + const gchar *gsttag2; +} 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,}, + {0, NULL,} +}; + +/* qtdemux produces these for atoms it cannot parse */ +#define GST_QT_DEMUX_PRIVATE_TAG "private-qt-tag" + +static void +gst_qt_mux_add_metadata_tags (GstQTMux * qtmux, const GstTagList * list) +{ + guint32 fourcc; + gint i; + const gchar *tag, *tag2; + + 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; + + if (!gst_tag_list_get_string (list, tag, &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; + GDateYear year; + GDateMonth month; + GDateDay day; + gchar *str; + + if (!gst_tag_list_get_date (list, tag, &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; + } + } + } + + /* add unparsed blobs if present */ + if (gst_tag_exists (GST_QT_DEMUX_PRIVATE_TAG)) { + guint num_tags; + + num_tags = gst_tag_list_get_tag_size (list, GST_QT_DEMUX_PRIVATE_TAG); + for (i = 0; i < num_tags; ++i) { + const GValue *val; + GstBuffer *buf; + GstCaps *caps = NULL; + + val = gst_tag_list_get_value_index (list, GST_QT_DEMUX_PRIVATE_TAG, i); + buf = (GstBuffer *) gst_value_get_mini_object (val); + + if (buf && (caps = gst_buffer_get_caps (buf))) { + GstStructure *s; + const gchar *style = NULL; + + GST_DEBUG_OBJECT (qtmux, "Found private tag %d/%d; size %d, caps %" + 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) { + GST_DEBUG_OBJECT (qtmux, "Adding private tag"); + atom_moov_add_blob_tag (qtmux->moov, GST_BUFFER_DATA (buf), + GST_BUFFER_SIZE (buf)); + } + } + gst_caps_unref (caps); + } + } + } + + return; +} + +/* + * Gets the tagsetter iface taglist and puts the known tags + * into the output stream + */ +static void +gst_qt_mux_setup_metadata (GstQTMux * qtmux) +{ + const GstTagList *user_tags; + GstTagList *mixedtags = NULL; + GstTagMergeMode merge_mode; + + user_tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (qtmux)); + merge_mode = gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (qtmux)); + + GST_DEBUG_OBJECT (qtmux, "merging tags, merge mode = %d", merge_mode); + GST_LOG_OBJECT (qtmux, "event tags: %" GST_PTR_FORMAT, qtmux->tags); + GST_LOG_OBJECT (qtmux, "set tags: %" GST_PTR_FORMAT, user_tags); + + mixedtags = gst_tag_list_merge (user_tags, qtmux->tags, merge_mode); + + GST_LOG_OBJECT (qtmux, "final tags: %" GST_PTR_FORMAT, mixedtags); + + if (mixedtags && !gst_tag_list_is_empty (mixedtags)) { + GST_DEBUG_OBJECT (qtmux, "Parsing tags"); + gst_qt_mux_add_metadata_tags (qtmux, mixedtags); + } else { + GST_DEBUG_OBJECT (qtmux, "No tags found"); + } + + if (mixedtags) + gst_tag_list_free (mixedtags); + + return; +} + +static GstFlowReturn +gst_qt_mux_send_buffer (GstQTMux * qtmux, GstBuffer * buf, guint64 * offset, + gboolean mind_fast) +{ + GstFlowReturn res; + guint8 *data; + guint size; + + g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR); + + data = GST_BUFFER_DATA (buf); + size = GST_BUFFER_SIZE (buf); + + GST_LOG_OBJECT (qtmux, "sending buffer size %d", size); + + if (mind_fast && qtmux->fast_start_file) { + gint ret; + + GST_LOG_OBJECT (qtmux, "to temporary file"); + ret = fwrite (data, sizeof (guint8), size, qtmux->fast_start_file); + gst_buffer_unref (buf); + if (ret != size) + goto write_error; + else + res = GST_FLOW_OK; + } else { + GST_LOG_OBJECT (qtmux, "downstream"); + + gst_buffer_set_caps (buf, GST_PAD_CAPS (qtmux->srcpad)); + res = gst_pad_push (qtmux->srcpad, buf); + } + + if (offset) + *offset += size; + + return res; + + /* ERRORS */ +write_error: + { + GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE, + ("Failed to write to temporary file"), GST_ERROR_SYSTEM); + return GST_FLOW_ERROR; + } +} + +static GstFlowReturn +gst_qt_mux_send_buffered_data (GstQTMux * qtmux, guint64 * offset) +{ + GstFlowReturn ret = GST_FLOW_OK; + GstBuffer *buf = NULL; + + if (fflush (qtmux->fast_start_file)) + goto flush_failed; + + if (fseek (qtmux->fast_start_file, 0, SEEK_SET)) + goto seek_failed; + + /* hm, this could all take a really really long time, + * but there may not be another way to get moov atom first + * (somehow optimize copy?) */ + GST_DEBUG_OBJECT (qtmux, "Sending buffered data"); + while (ret == GST_FLOW_OK) { + gint r; + const int bufsize = 4096; + + buf = gst_buffer_new_and_alloc (bufsize); + r = fread (GST_BUFFER_DATA (buf), sizeof (guint8), bufsize, + qtmux->fast_start_file); + if (r == 0) + break; + GST_BUFFER_SIZE (buf) = r; + GST_LOG_OBJECT (qtmux, "Pushing buffered buffer of size %d", r); + ret = gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE); + buf = NULL; + } + if (buf) + gst_buffer_unref (buf); + +exit: + /* best cleaning up effort, eat possible error */ + fclose (qtmux->fast_start_file); + qtmux->fast_start_file = NULL; + + /* FIXME maybe delete temporary file, or let the system handle that ? */ + + return ret; + + /* ERRORS */ +flush_failed: + { + GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE, + ("Failed to flush temporary file"), GST_ERROR_SYSTEM); + ret = GST_FLOW_ERROR; + goto exit; + } +seek_failed: + { + GST_ELEMENT_ERROR (qtmux, RESOURCE, SEEK, + ("Failed to seek temporary file"), GST_ERROR_SYSTEM); + ret = GST_FLOW_ERROR; + goto exit; + } +} + +/* + * Sends the initial mdat atom fields (size fields and fourcc type), + * the subsequent buffers are considered part of it's data. + * As we can't predict the amount of data that we are going to place in mdat + * we need to record the position of the size field in the stream so we can + * 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) +{ + Atom *node_header; + GstBuffer *buf; + guint8 *data = NULL; + guint64 offset = 0; + + GST_DEBUG_OBJECT (qtmux, "Sending mdat's atom header, " + "size %" G_GUINT64_FORMAT, 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; + + size = offset = 0; + if (atom_copy_data (node_header, &data, &size, &offset) == 0) + goto serialize_error; + + buf = gst_buffer_new (); + GST_BUFFER_DATA (buf) = GST_BUFFER_MALLOCDATA (buf) = data; + GST_BUFFER_SIZE (buf) = offset; + + g_free (node_header); + + GST_LOG_OBJECT (qtmux, "Pushing mdat start"); + return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE); + + /* ERRORS */ +serialize_error: + { + GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL), + ("Failed to serialize ftyp")); + return GST_FLOW_ERROR; + } +} + +/* + * We get the position of the mdat size field, seek back to it + * and overwrite with the real value + */ +static GstFlowReturn +gst_qt_mux_update_mdat_size (GstQTMux * qtmux, guint64 mdat_pos, + guint64 mdat_size, guint64 * offset) +{ + GstEvent *event; + GstBuffer *buf; + + /* 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); + + return gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE); +} + +static GstFlowReturn +gst_qt_mux_stop_file (GstQTMux * qtmux) +{ + gboolean ret = GST_FLOW_OK; + GstBuffer *buffer = NULL; + guint64 offset = 0, size = 0; + guint8 *data; + GSList *walk; + gboolean large_file; + guint32 timescale; + + GST_DEBUG_OBJECT (qtmux, "Updating remaining values and sending last data"); + + /* pushing last buffers for each pad */ + for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) { + GstCollectData *cdata = (GstCollectData *) walk->data; + GstQTPad *qtpad = (GstQTPad *) cdata; + + /* send last buffer */ + GST_DEBUG_OBJECT (qtmux, "Sending the last buffer for pad %s", + GST_PAD_NAME (qtpad->collect.pad)); + ret = gst_qt_mux_add_buffer (qtmux, qtpad, NULL); + if (ret != GST_FLOW_OK) + GST_DEBUG_OBJECT (qtmux, "Failed to send last buffer for %s, " + "flow return: %s", GST_PAD_NAME (qtpad->collect.pad), + gst_flow_get_name (ret)); + } + + GST_OBJECT_LOCK (qtmux); + timescale = qtmux->timescale; + large_file = qtmux->large_file; + GST_OBJECT_UNLOCK (qtmux); + + /* inform lower layers of our property wishes, and determine duration. + * Let moov take care of this using its list of traks; + * so that released pads are also included */ + GST_DEBUG_OBJECT (qtmux, "Large file support: %d", large_file); + GST_DEBUG_OBJECT (qtmux, "Updating timescale to %" G_GUINT32_FORMAT, + timescale); + atom_moov_update_timescale (qtmux->moov, timescale); + atom_moov_set_64bits (qtmux->moov, large_file); + atom_moov_update_duration (qtmux->moov); + + /* tags into file metadata */ + gst_qt_mux_setup_metadata (qtmux); + + /* 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) { + /* copy into NULL to obtain size */ + offset = size = 0; + if (!atom_moov_copy_data (qtmux->moov, NULL, &size, &offset)) + goto serialize_error; + GST_DEBUG_OBJECT (qtmux, "calculated moov atom size %" G_GUINT64_FORMAT, + size); + offset += qtmux->header_size + MDAT_ATOM_HEADER_SIZE; + } else + offset = qtmux->header_size; + atom_moov_chunks_add_offset (qtmux->moov, offset); + + /* serialize moov */ + offset = size = 0; + data = NULL; + GST_LOG_OBJECT (qtmux, "Copying movie header into buffer"); + ret = atom_moov_copy_data (qtmux->moov, &data, &size, &offset); + if (!ret) + goto serialize_error; + + buffer = gst_buffer_new (); + GST_BUFFER_DATA (buffer) = GST_BUFFER_MALLOCDATA (buffer) = data; + GST_BUFFER_SIZE (buffer) = offset; + /* note: as of this point, we no longer care about tracking written data size, + * since there is no more use for it anyway */ + 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); + if (ret != GST_FLOW_OK) + return ret; + ret = gst_qt_mux_send_buffered_data (qtmux, NULL); + if (ret != GST_FLOW_OK) + return ret; + } else { + /* mdata needs update iff not using faststart */ + GST_DEBUG_OBJECT (qtmux, "updating mdata size"); + ret = gst_qt_mux_update_mdat_size (qtmux, qtmux->mdat_pos, + qtmux->mdat_size, NULL); + /* note; no seeking back to the end of file is done, + * since we longer write anything anyway */ + } + + return ret; + + /* ERRORS */ +serialize_error: + { + gst_buffer_unref (buffer); + GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL), + ("Failed to serialize moov")); + return GST_FLOW_ERROR; + } +} + +static GstFlowReturn +gst_qt_mux_send_ftyp (GstQTMux * qtmux, guint64 * off) +{ + GstBuffer *buf; + guint64 size = 0, offset = 0; + guint8 *data = NULL; + + GST_DEBUG_OBJECT (qtmux, "Sending ftyp atom"); + + if (!atom_ftyp_copy_data (qtmux->ftyp, &data, &size, &offset)) + goto serialize_error; + + buf = gst_buffer_new (); + GST_BUFFER_DATA (buf) = GST_BUFFER_MALLOCDATA (buf) = data; + GST_BUFFER_SIZE (buf) = offset; + + GST_LOG_OBJECT (qtmux, "Pushing ftyp"); + return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE); + + /* ERRORS */ +serialize_error: + { + GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL), + ("Failed to serialize ftyp")); + return GST_FLOW_ERROR; + } +} + +static GstFlowReturn +gst_qt_mux_start_file (GstQTMux * qtmux) +{ + GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux)); + GstFlowReturn ret = GST_FLOW_OK; + guint32 major, version; + GList *comp; + GstBuffer *prefix; + + GST_DEBUG_OBJECT (qtmux, "starting file"); + + /* let downstream know we think in BYTES and expect to do seeking later on */ + gst_pad_push_event (qtmux->srcpad, + gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, 0, -1, 0)); + + /* init and send context and ftyp based on current property state */ + if (qtmux->ftyp) + atom_ftyp_free (qtmux->ftyp); + gst_qt_mux_map_format_to_header (qtmux_klass->format, &prefix, &major, + &version, &comp, qtmux->moov); + qtmux->ftyp = atom_ftyp_new (qtmux->context, major, version, comp); + if (comp) + g_list_free (comp); + if (prefix) { + ret = gst_qt_mux_send_buffer (qtmux, prefix, &qtmux->header_size, FALSE); + if (ret != GST_FLOW_OK) + goto exit; + } + ret = gst_qt_mux_send_ftyp (qtmux, &qtmux->header_size); + if (ret != GST_FLOW_OK) + goto exit; + + /* send mdat header if already needed, and mark position for later update */ + GST_OBJECT_LOCK (qtmux); + if (qtmux->fast_start) { + qtmux->fast_start_file = g_fopen (qtmux->fast_start_file_path, "wb+"); + 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); + } + GST_OBJECT_UNLOCK (qtmux); + +exit: + return ret; + + /* ERRORS */ +open_failed: + { + GST_ELEMENT_ERROR (qtmux, RESOURCE, OPEN_READ_WRITE, + (("Could not open temporary file \"%s\""), qtmux->fast_start_file_path), + GST_ERROR_SYSTEM); + GST_OBJECT_UNLOCK (qtmux); + return GST_FLOW_ERROR; + } +} + +/* + * Here we push the buffer and update the tables in the track atoms + */ +static GstFlowReturn +gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf) +{ + GstBuffer *last_buf = NULL; + GstClockTime duration; + guint nsamples, sample_size; + guint64 scaled_duration, chunk_offset; + guint64 last_dts; + gint64 pts_offset = 0; + gboolean sync = FALSE, do_pts = FALSE; + + if (!pad->fourcc) + goto not_negotiated; + + last_buf = pad->last_buf; + if (last_buf == NULL) { +#ifndef GST_DISABLE_GST_DEBUG + if (buf == NULL) { + GST_DEBUG_OBJECT (qtmux, "Pad %s has no previous buffer stored and " + "received NULL buffer, doing nothing", + GST_PAD_NAME (pad->collect.pad)); + } else { + GST_LOG_OBJECT (qtmux, + "Pad %s has no previous buffer stored, storing now", + GST_PAD_NAME (pad->collect.pad)); + } +#endif + pad->last_buf = buf; + return GST_FLOW_OK; + } else + gst_buffer_ref (last_buf); + + /* fall back to duration if: + * - last bufer + * - this format has out of order buffers (e.g. MPEG-4), + * - lack of valid time forces fall back */ + if (buf == NULL || pad->is_out_of_order || + !GST_BUFFER_TIMESTAMP_IS_VALID (last_buf) || + !GST_BUFFER_TIMESTAMP_IS_VALID (buf)) { + if (!GST_BUFFER_DURATION_IS_VALID (last_buf)) { + /* be forgiving for some possibly last upstream flushed buffer */ + if (buf) + goto no_time; + GST_WARNING_OBJECT (qtmux, "no duration for last buffer"); + /* iso spec recommends some small value, try 0 */ + duration = 0; + } else + duration = GST_BUFFER_DURATION (last_buf); + } else { + duration = GST_BUFFER_TIMESTAMP (buf) - GST_BUFFER_TIMESTAMP (last_buf); + } + + gst_buffer_replace (&pad->last_buf, buf); + + last_dts = gst_util_uint64_scale (pad->last_dts, + atom_trak_get_timescale (pad->trak), GST_SECOND); + + /* raw audio has many samples per buffer (= chunk) */ + if (pad->sample_size) { + sample_size = pad->sample_size; + if (GST_BUFFER_SIZE (last_buf) % sample_size != 0) + goto fragmented_sample; + /* note: qt raw audio storage warps it implicitly into a timewise + * perfect stream, discarding buffer times */ + nsamples = GST_BUFFER_SIZE (last_buf) / sample_size; + duration = GST_BUFFER_DURATION (last_buf) / nsamples; + /* timescale = samplerate */ + scaled_duration = 1; + } else { + nsamples = 1; + sample_size = GST_BUFFER_SIZE (last_buf); + /* first convert intended timestamp (in GstClockTime resolution) to + * trak timescale, then derive delta; + * this ensures sums of (scale)delta add up to converted timestamp, + * which only deviates at most 1/scale from timestamp itself */ + scaled_duration = gst_util_uint64_scale (pad->last_dts + duration, + atom_trak_get_timescale (pad->trak), GST_SECOND) - last_dts; + } + pad->last_dts += duration * nsamples; + chunk_offset = qtmux->mdat_size; + + GST_LOG_OBJECT (qtmux, + "Pad (%s) dts updated to %" GST_TIME_FORMAT, + GST_PAD_NAME (pad->collect.pad), GST_TIME_ARGS (pad->last_dts)); + GST_LOG_OBJECT (qtmux, + "Adding %d samples to track, duration: %" G_GUINT64_FORMAT + " size: %" G_GUINT32_FORMAT " chunk offset: %" G_GUINT64_FORMAT, + nsamples, scaled_duration, sample_size, chunk_offset); + + /* might be a sync sample */ + if (pad->sync && + !GST_BUFFER_FLAG_IS_SET (last_buf, GST_BUFFER_FLAG_DELTA_UNIT)) { + GST_LOG_OBJECT (qtmux, "Adding new sync sample entry for track of pad %s", + GST_PAD_NAME (pad->collect.pad)); + sync = TRUE; + } + + /* optionally calculate ctts entry values + * (if composition-time expected different from decoding-time) */ + /* really not recommended: + * - decoder typically takes care of dts/pts issues + * - in case of out-of-order, dts may only be determined as above + * (e.g. sum of duration), which may be totally different from + * buffer timestamps in case of multiple segment, non-perfect streams + * (and just perhaps maybe with some luck segment_to_running_time + * or segment_to_media_time might get near to it) */ + if (qtmux->do_ctts && pad->is_out_of_order) { + guint64 pts; + + pts = gst_util_uint64_scale (GST_BUFFER_TIMESTAMP (last_buf), + atom_trak_get_timescale (pad->trak), GST_SECOND); + pts_offset = (gint64) (pts - last_dts); + do_pts = TRUE; + GST_LOG_OBJECT (qtmux, "Adding ctts entry for pad %s: %" G_GUINT64_FORMAT, + GST_PAD_NAME (pad->collect.pad), pts_offset); + } + + /* now we go and register this buffer/sample all over */ + /* note that a new chunk is started each time (not fancy but works) */ + atom_trak_add_samples (pad->trak, nsamples, scaled_duration, sample_size, + chunk_offset, sync, do_pts, pts_offset); + + if (buf) + gst_buffer_unref (buf); + + return gst_qt_mux_send_buffer (qtmux, last_buf, &qtmux->mdat_size, TRUE); + + /* ERRORS */ +bail: + { + if (buf) + gst_buffer_unref (buf); + gst_buffer_unref (last_buf); + return GST_FLOW_ERROR; + } +no_time: + { + GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL), + ("Failed to determine time to mux.")); + goto bail; + } +fragmented_sample: + { + GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL), + ("Audio buffer contains fragmented sample.")); + goto bail; + } +not_negotiated: + { + GST_ELEMENT_ERROR (qtmux, CORE, NEGOTIATION, (NULL), + ("format wasn't negotiated before buffer flow on pad %s", + GST_PAD_NAME (pad->collect.pad))); + gst_buffer_unref (buf); + return GST_FLOW_NOT_NEGOTIATED; + } +} + +static GstFlowReturn +gst_qt_mux_collected (GstCollectPads * pads, gpointer user_data) +{ + GstFlowReturn ret = GST_FLOW_OK; + GstQTMux *qtmux = GST_QT_MUX_CAST (user_data); + GSList *walk; + GstQTPad *best_pad = NULL; + GstClockTime time, best_time = GST_CLOCK_TIME_NONE; + GstBuffer *buf; + + if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_STARTED)) { + if ((ret = gst_qt_mux_start_file (qtmux)) != GST_FLOW_OK) + return ret; + else + qtmux->state = GST_QT_MUX_STATE_DATA; + } + + if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_EOS)) + return GST_FLOW_UNEXPECTED; + + /* select the best buffer */ + walk = qtmux->collect->data; + while (walk) { + GstQTPad *pad; + GstCollectData *data; + + data = (GstCollectData *) walk->data; + pad = (GstQTPad *) data; + + walk = g_slist_next (walk); + + buf = gst_collect_pads_peek (pads, data); + if (buf == NULL) { + GST_LOG_OBJECT (qtmux, "Pad %s has no buffers", + GST_PAD_NAME (pad->collect.pad)); + continue; + } + time = GST_BUFFER_TIMESTAMP (buf); + gst_buffer_unref (buf); + + if (best_pad == NULL || !GST_CLOCK_TIME_IS_VALID (time) || + (GST_CLOCK_TIME_IS_VALID (best_time) && time < best_time)) { + best_pad = pad; + best_time = time; + } + } + + if (best_pad != NULL) { + GST_LOG_OBJECT (qtmux, "selected pad %s with time %" GST_TIME_FORMAT, + GST_PAD_NAME (best_pad->collect.pad), GST_TIME_ARGS (best_time)); + buf = gst_collect_pads_pop (pads, &best_pad->collect); + ret = gst_qt_mux_add_buffer (qtmux, best_pad, buf); + } else { + ret = gst_qt_mux_stop_file (qtmux); + if (ret == GST_FLOW_OK) { + gst_pad_push_event (qtmux->srcpad, gst_event_new_eos ()); + ret = GST_FLOW_UNEXPECTED; + } + qtmux->state = GST_QT_MUX_STATE_EOS; + } + + return ret; +} + +static gboolean +gst_qt_mux_audio_sink_set_caps (GstPad * pad, GstCaps * caps) +{ + GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad)); + GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux)); + GstQTPad *qtpad = NULL; + GstStructure *structure; + const gchar *mimetype; + gint rate, channels; + const GValue *value = NULL; + const GstBuffer *codec_data = NULL; + GstQTMuxFormat format; + AudioSampleEntry entry = { 0, }; + AtomInfo *ext_atom = NULL; + gint constant_size = 0; + guint esds_type = 0; + + /* find stream data */ + qtpad = (GstQTPad *) gst_pad_get_element_private (pad); + g_assert (qtpad); + + /* does not go well to renegotiate stream mid-way */ + if (qtpad->fourcc) + goto refuse_renegotiation; + + GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT, + GST_DEBUG_PAD_NAME (pad), caps); + + format = qtmux_klass->format; + structure = gst_caps_get_structure (caps, 0); + mimetype = gst_structure_get_name (structure); + + /* common info */ + if (!gst_structure_get_int (structure, "channels", &channels) || + !gst_structure_get_int (structure, "rate", &rate)) { + goto refuse_caps; + } + + /* optional */ + value = gst_structure_get_value (structure, "codec_data"); + if (value != NULL) + codec_data = gst_value_get_buffer (value); + + qtpad->is_out_of_order = FALSE; + + /* set common properties */ + entry.sample_rate = rate; + entry.channels = channels; + /* default */ + entry.sample_size = 16; + /* this is the typical compressed case */ + if (format == GST_QT_MUX_FORMAT_QT) { + entry.version = 1; + entry.compression_id = -2; + } + + /* now map onto a fourcc, and some extra properties */ + if (strcmp (mimetype, "audio/mpeg") == 0) { + gint mpegversion = 0; + gint layer = -1; + + gst_structure_get_int (structure, "mpegversion", &mpegversion); + switch (mpegversion) { + case 1: + gst_structure_get_int (structure, "layer", &layer); + switch (layer) { + case 3: + /* mp3 */ + /* note: QuickTime player does not like mp3 either way in iso/mp4 */ + if (format == GST_QT_MUX_FORMAT_QT) + entry.fourcc = FOURCC__mp3; + else { + entry.fourcc = FOURCC_mp4a; + esds_type = ESDS_OBJECT_TYPE_MPEG1_P3; + } + entry.samples_per_packet = 1152; + entry.bytes_per_sample = 2; + break; + } + break; + case 4: + /* AAC */ + entry.fourcc = FOURCC_mp4a; + esds_type = ESDS_OBJECT_TYPE_MPEG4_P3; + if (!codec_data || GST_BUFFER_SIZE (codec_data) < 2) + GST_WARNING_OBJECT (qtmux, "no (valid) codec_data for AAC audio"); + else { + guint8 profile = GST_READ_UINT8 (GST_BUFFER_DATA (codec_data)); + + /* warn if not Low Complexity profile */ + profile >>= 3; + if (profile != 2) + GST_WARNING_OBJECT (qtmux, + "non-LC AAC may not run well on (Apple) QuickTime/iTunes"); + } + break; + default: + break; + } + } else if (strcmp (mimetype, "audio/AMR") == 0) { + entry.fourcc = FOURCC_samr; + entry.sample_size = 16; + entry.samples_per_packet = 160; + entry.bytes_per_sample = 2; + } else if (strcmp (mimetype, "audio/x-raw-int") == 0) { + gint width; + gint depth; + gint endianness; + gboolean sign; + + if (!gst_structure_get_int (structure, "width", &width) || + !gst_structure_get_int (structure, "depth", &depth) || + !gst_structure_get_boolean (structure, "signed", &sign) || + !gst_structure_get_int (structure, "endianness", &endianness)) { + GST_DEBUG_OBJECT (qtmux, + "broken caps, width/depth/signed/endianness field missing"); + goto refuse_caps; + } + + /* spec has no place for a distinction in these */ + if (width != depth) { + GST_DEBUG_OBJECT (qtmux, "width must be same as depth!"); + goto refuse_caps; + } + + if (sign) { + if (endianness == G_LITTLE_ENDIAN) + entry.fourcc = FOURCC_sowt; + else if (endianness == G_BIG_ENDIAN) + entry.fourcc = FOURCC_twos; + /* maximum backward compatibility; only new version for > 16 bit */ + if (depth <= 16) + entry.version = 0; + /* not compressed in any case */ + entry.compression_id = 0; + /* QT spec says: max at 16 bit even if sample size were actually larger, + * however, most players (e.g. QuickTime!) seem to disagree, so ... */ + entry.sample_size = depth; + entry.bytes_per_sample = depth / 8; + entry.samples_per_packet = 1; + entry.bytes_per_packet = depth / 8; + entry.bytes_per_frame = entry.bytes_per_packet * channels; + } else { + if (width == 8 && depth == 8) { + /* fall back to old 8-bit version */ + entry.fourcc = FOURCC_raw_; + entry.version = 0; + entry.compression_id = 0; + entry.sample_size = 8; + } else { + GST_DEBUG_OBJECT (qtmux, "non 8-bit PCM must be signed"); + goto refuse_caps; + } + } + constant_size = (depth / 8) * channels; + } else if (strcmp (mimetype, "audio/x-alaw") == 0) { + entry.fourcc = FOURCC_alaw; + entry.samples_per_packet = 1023; + entry.bytes_per_sample = 2; + } else if (strcmp (mimetype, "audio/x-mulaw") == 0) { + entry.fourcc = FOURCC_ulaw; + entry.samples_per_packet = 1023; + entry.bytes_per_sample = 2; + } + + if (!entry.fourcc) + goto refuse_caps; + + /* ok, set the pad info accordingly */ + qtpad->fourcc = entry.fourcc; + qtpad->sample_size = constant_size; + /* collect optional extensions */ + ext_atom = build_sample_entry_extension (qtpad->trak, qtmux->context->flavor, + entry.fourcc, esds_type, codec_data); + atom_trak_set_audio_type (qtpad->trak, qtmux->context, &entry, + entry.sample_rate, ext_atom, constant_size); + + gst_object_unref (qtmux); + return TRUE; + + /* ERRORS */ +refuse_caps: + { + GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT, + GST_PAD_NAME (pad), caps); + gst_object_unref (qtmux); + return FALSE; + } +refuse_renegotiation: + { + GST_WARNING_OBJECT (qtmux, + "pad %s refused renegotiation to %" GST_PTR_FORMAT, + GST_PAD_NAME (pad), caps); + gst_object_unref (qtmux); + return FALSE; + } +} + +/* scale rate up or down by factor of 10 to fit into [1000,10000] interval */ +static guint32 +adjust_rate (guint64 rate) +{ + while (rate >= 10000) + rate /= 10; + + while (rate < 1000) + rate *= 10; + + return (guint32) rate; +} + +static gboolean +gst_qt_mux_video_sink_set_caps (GstPad * pad, GstCaps * caps) +{ + GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad)); + GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux)); + GstQTPad *qtpad = NULL; + GstStructure *structure; + const gchar *mimetype; + gint width, height, depth = -1; + gint framerate_num, framerate_den; + guint32 rate; + const GValue *value = NULL; + const GstBuffer *codec_data = NULL; + VisualSampleEntry entry = { 0, }; + GstQTMuxFormat format; + AtomInfo *ext_atom = NULL; + guint esds_type = 0; + gboolean sync = FALSE; + + /* find stream data */ + qtpad = (GstQTPad *) gst_pad_get_element_private (pad); + g_assert (qtpad); + + /* does not go well to renegotiate stream mid-way */ + if (qtpad->fourcc) + goto refuse_renegotiation; + + GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT, + GST_DEBUG_PAD_NAME (pad), caps); + + format = qtmux_klass->format; + structure = gst_caps_get_structure (caps, 0); + mimetype = gst_structure_get_name (structure); + + /* required parts */ + if (!gst_structure_get_int (structure, "width", &width) || + !gst_structure_get_int (structure, "height", &height) || + !gst_structure_get_fraction (structure, "framerate", &framerate_num, + &framerate_den)) + goto refuse_caps; + + /* optional */ + gst_structure_get_int (structure, "depth", &depth); + value = gst_structure_get_value (structure, "codec_data"); + if (value != NULL) + codec_data = gst_value_get_buffer (value); + + /* FIXME: pixel-aspect-ratio */ + + qtpad->is_out_of_order = FALSE; + + /* bring frame numerator into a range that ensures both reasonable resolution + * as well as a fair duration */ + rate = adjust_rate (framerate_num); + GST_DEBUG_OBJECT (qtmux, "Rate of video track selected: %" G_GUINT32_FORMAT, + rate); + + /* set common properties */ + entry.width = width; + entry.height = height; + /* should be OK according to qt and iso spec, override if really needed */ + entry.color_table_id = -1; + entry.frame_count = 1; + entry.depth = 24; + + /* sync entries by default */ + sync = TRUE; + + /* now map onto a fourcc, and some extra properties */ + if (strcmp (mimetype, "video/x-raw-rgb") == 0) { + gint bpp; + + entry.fourcc = FOURCC_raw_; + gst_structure_get_int (structure, "bpp", &bpp); + entry.depth = bpp; + sync = FALSE; + } else if (strcmp (mimetype, "video/x-raw-yuv") == 0) { + guint32 format = 0; + + sync = FALSE; + gst_structure_get_fourcc (structure, "format", &format); + switch (format) { + case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'): + if (depth == -1) + depth = 24; + entry.fourcc = FOURCC_2vuy; + entry.depth = depth; + break; + } + } else if (strcmp (mimetype, "video/x-h263") == 0) { + entry.fourcc = FOURCC_h263; + } else if (strcmp (mimetype, "video/x-divx") == 0 || + strcmp (mimetype, "video/mpeg") == 0) { + gint version = 0; + + if (strcmp (mimetype, "video/x-divx") == 0) { + gst_structure_get_int (structure, "divxversion", &version); + version = version == 5 ? 1 : 0; + } else { + gst_structure_get_int (structure, "mpegversion", &version); + version = version == 4 ? 1 : 0; + } + if (version) { + entry.fourcc = FOURCC_mp4v; + esds_type = ESDS_OBJECT_TYPE_MPEG4_P2; + if (!codec_data) + GST_WARNING_OBJECT (qtmux, "no codec_data for MPEG4 video; " + "output might not play in Apple QuickTime (try global-headers?)"); + } + } else if (strcmp (mimetype, "video/x-h264") == 0) { + entry.fourcc = FOURCC_avc1; + qtpad->is_out_of_order = TRUE; + if (!codec_data) + GST_WARNING_OBJECT (qtmux, "no codec_data in h264 caps"); + } else if (strcmp (mimetype, "video/x-dv") == 0) { + gint version = 0; + gboolean pal = TRUE; + + sync = FALSE; + if (framerate_num != 25 || framerate_den != 1) + pal = FALSE; + gst_structure_get_int (structure, "dvversion", &version); + /* fall back to typical one */ + if (!version) + version = 25; + switch (version) { + case 25: + if (pal) + entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', 'p'); + else + entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', ' '); + break; + case 50: + if (pal) + entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'p'); + else + entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'n'); + break; + default: + GST_WARNING_OBJECT (qtmux, "unrecognized dv version"); + break; + } + } else if (strcmp (mimetype, "image/jpeg") == 0) { + entry.fourcc = FOURCC_jpeg; + sync = FALSE; + } else if (strcmp (mimetype, "video/x-qt-part") == 0) { + guint32 fourcc; + + gst_structure_get_fourcc (structure, "format", &fourcc); + entry.fourcc = fourcc; + qtpad->is_out_of_order = TRUE; + } + + if (!entry.fourcc) + goto refuse_caps; + + /* ok, set the pad info accordingly */ + qtpad->fourcc = entry.fourcc; + qtpad->sync = sync; + /* collect optional extensions */ + ext_atom = build_sample_entry_extension (qtpad->trak, qtmux->context->flavor, + entry.fourcc, esds_type, codec_data); + atom_trak_set_video_type (qtpad->trak, qtmux->context, &entry, rate, + ext_atom); + + gst_object_unref (qtmux); + return TRUE; + + /* ERRORS */ +refuse_caps: + { + GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT, + GST_PAD_NAME (pad), caps); + gst_object_unref (qtmux); + return FALSE; + } +refuse_renegotiation: + { + GST_WARNING_OBJECT (qtmux, + "pad %s refused renegotiation to %" GST_PTR_FORMAT, + GST_PAD_NAME (pad), caps); + gst_object_unref (qtmux); + return FALSE; + } +} + +static gboolean +gst_qt_mux_sink_event (GstPad * pad, GstEvent * event) +{ + gboolean ret; + GstQTMux *qtmux; + GstTagList *list; + + qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad)); + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_TAG: + GST_DEBUG_OBJECT (qtmux, "received tag event"); + gst_event_parse_tag (event, &list); + + if (qtmux->tags) { + gst_tag_list_insert (qtmux->tags, list, GST_TAG_MERGE_PREPEND); + } else { + qtmux->tags = gst_tag_list_copy (list); + } + break; + default: + break; + } + + ret = qtmux->collect_event (pad, event); + gst_object_unref (qtmux); + + return ret; +} + +static void +gst_qt_mux_release_pad (GstElement * element, GstPad * pad) +{ + GstQTMux *mux = GST_QT_MUX_CAST (element); + + /* let GstCollectPads complain if it is some unknown pad */ + if (gst_collect_pads_remove_pad (mux->collect, pad)) + gst_element_remove_pad (element, pad); +} + +static GstPad * +gst_qt_mux_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * name) +{ + GstElementClass *klass = GST_ELEMENT_GET_CLASS (element); + GstQTMux *qtmux = GST_QT_MUX_CAST (element); + GstQTPad *collect_pad; + GstPad *newpad; + gboolean audio; + + GST_DEBUG_OBJECT (qtmux, "Requested pad: %s", GST_STR_NULL (name)); + + if (qtmux->state != GST_QT_MUX_STATE_NONE) { + GST_WARNING_OBJECT (qtmux, "Not providing request pad after stream start."); + return NULL; + } + + if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) { + audio = TRUE; + } else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) { + audio = FALSE; + } else { + GST_WARNING_OBJECT (qtmux, "This is not our template!"); + return NULL; + } + + /* add pad to collections */ + newpad = gst_pad_new_from_template (templ, name); + collect_pad = (GstQTPad *) + gst_collect_pads_add_pad_full (qtmux->collect, newpad, sizeof (GstQTPad), + (GstCollectDataDestroyNotify) (gst_qt_mux_pad_reset)); + /* set up pad */ + gst_qt_mux_pad_reset (collect_pad); + collect_pad->trak = atom_trak_new (qtmux->context); + atom_moov_add_trak (qtmux->moov, collect_pad->trak); + + /* set up pad functions */ + if (audio) + gst_pad_set_setcaps_function (newpad, + GST_DEBUG_FUNCPTR (gst_qt_mux_audio_sink_set_caps)); + else + gst_pad_set_setcaps_function (newpad, + GST_DEBUG_FUNCPTR (gst_qt_mux_video_sink_set_caps)); + + /* FIXME: hacked way to override/extend the event function of + * GstCollectPads; because it sets its own event function giving the + * element no access to events. + */ + qtmux->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (newpad); + gst_pad_set_event_function (newpad, + GST_DEBUG_FUNCPTR (gst_qt_mux_sink_event)); + + gst_pad_set_active (newpad, TRUE); + gst_element_add_pad (element, newpad); + + return newpad; +} + +static void +gst_qt_mux_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstQTMux *qtmux = GST_QT_MUX_CAST (object); + + GST_OBJECT_LOCK (qtmux); + switch (prop_id) { + case PROP_LARGE_FILE: + g_value_set_boolean (value, qtmux->large_file); + break; + case PROP_MOVIE_TIMESCALE: + g_value_set_uint (value, qtmux->timescale); + break; + case PROP_DO_CTTS: + g_value_set_boolean (value, qtmux->do_ctts); + break; + case PROP_FAST_START: + g_value_set_boolean (value, qtmux->fast_start); + break; + case PROP_FAST_START_TEMP_FILE: + g_value_set_string (value, qtmux->fast_start_file_path); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + GST_OBJECT_UNLOCK (qtmux); +} + +static void +gst_qt_mux_generate_fast_start_file_path (GstQTMux * qtmux) +{ + gchar *tmp; + + if (qtmux->fast_start_file_path) { + g_free (qtmux->fast_start_file_path); + qtmux->fast_start_file_path = NULL; + } + + tmp = g_strdup_printf ("%s%d", "qtmux", g_random_int ()); + qtmux->fast_start_file_path = g_build_filename (g_get_tmp_dir (), tmp, NULL); + g_free (tmp); +} + +static void +gst_qt_mux_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstQTMux *qtmux = GST_QT_MUX_CAST (object); + + GST_OBJECT_LOCK (qtmux); + switch (prop_id) { + case PROP_LARGE_FILE: + qtmux->large_file = g_value_get_boolean (value); + break; + case PROP_MOVIE_TIMESCALE: + qtmux->timescale = g_value_get_uint (value); + break; + case PROP_DO_CTTS: + qtmux->do_ctts = g_value_get_boolean (value); + break; + case PROP_FAST_START: + qtmux->fast_start = g_value_get_boolean (value); + break; + case PROP_FAST_START_TEMP_FILE: + if (qtmux->fast_start_file_path) { + g_free (qtmux->fast_start_file_path); + } + qtmux->fast_start_file_path = g_value_dup_string (value); + /* NULL means to generate a random one */ + if (!qtmux->fast_start_file_path) { + gst_qt_mux_generate_fast_start_file_path (qtmux); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + GST_OBJECT_UNLOCK (qtmux); +} + +static GstStateChangeReturn +gst_qt_mux_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret; + GstQTMux *qtmux = GST_QT_MUX_CAST (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + gst_collect_pads_start (qtmux->collect); + qtmux->state = GST_QT_MUX_STATE_STARTED; + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_collect_pads_stop (qtmux->collect); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_qt_mux_reset (qtmux, TRUE); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + break; + default: + break; + } + + return ret; +} + + +gboolean +gst_qt_mux_register (GstPlugin * plugin) +{ + GTypeInfo typeinfo = { + sizeof (GstQTMuxClass), + (GBaseInitFunc) gst_qt_mux_base_init, + NULL, + (GClassInitFunc) gst_qt_mux_class_init, + NULL, + NULL, + sizeof (GstQTMux), + 0, + (GInstanceInitFunc) gst_qt_mux_init, + }; + static const GInterfaceInfo tag_setter_info = { + NULL, NULL, NULL + }; + GType type; + GstQTMuxFormat format; + GstQTMuxClassParams *params; + guint i = 0; + + GST_LOG ("Registering muxers"); + + while (TRUE) { + GstQTMuxFormatProp *prop; + + prop = &gst_qt_mux_format_list[i]; + format = prop->format; + if (format == GST_QT_MUX_FORMAT_NONE) + break; + + /* create a cache for these properties */ + params = g_new0 (GstQTMuxClassParams, 1); + params->prop = prop; + params->src_caps = gst_static_caps_get (&prop->src_caps); + params->video_sink_caps = gst_static_caps_get (&prop->video_sink_caps); + params->audio_sink_caps = gst_static_caps_get (&prop->audio_sink_caps); + + /* create the type now */ + type = g_type_register_static (GST_TYPE_ELEMENT, prop->type_name, &typeinfo, + 0); + g_type_set_qdata (type, GST_QT_MUX_PARAMS_QDATA, (gpointer) params); + g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, &tag_setter_info); + + if (!gst_element_register (plugin, prop->name, GST_RANK_NONE, type)) + return FALSE; + + i++; + } + + GST_LOG ("Finished registering muxers"); + + return TRUE; +} + +gboolean +gst_qt_mux_plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (gst_qt_mux_debug, "qtmux", 0, "QT Muxer"); + + return gst_qt_mux_register (plugin); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "qtmux", + "Quicktime Muxer plugin", + gst_qt_mux_plugin_init, VERSION, "LGPL", "gsoc2008 package", + "embedded.ufcg.edu.br") diff --git a/gst/quicktime/gstqtmux.h b/gst/quicktime/gstqtmux.h new file mode 100644 index 000000000..4459947a4 --- /dev/null +++ b/gst/quicktime/gstqtmux.h @@ -0,0 +1,134 @@ +/* Quicktime muxer plugin for GStreamer + * Copyright (C) 2008 Thiago Sousa Santos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_QT_MUX_H__ +#define __GST_QT_MUX_H__ + +#include +#include + +#include "fourcc.h" +#include "atoms.h" +#include "gstqtmuxmap.h" + +G_BEGIN_DECLS + +#define GST_TYPE_QT_MUX (gst_qt_mux_get_type()) +#define GST_QT_MUX(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_QT_MUX, GstQTMux)) +#define GST_QT_MUX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_QT_MUX, GstQTMux)) +#define GST_IS_QT_MUX(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_QT_MUX)) +#define GST_IS_QT_MUX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_QT_MUX)) +#define GST_QT_MUX_CAST(obj) ((GstQTMux*)(obj)) + + +typedef struct _GstQTMux GstQTMux; +typedef struct _GstQTMuxClass GstQTMuxClass; + +typedef struct _GstQTPad +{ + GstCollectData collect; /* we extend the CollectData */ + + /* fourcc id of stream */ + guint32 fourcc; + /* whether using format that have out of order buffers */ + gboolean is_out_of_order; + /* if not 0, track with constant sized samples, e.g. raw audio */ + guint sample_size; + /* make sync table entry */ + gboolean sync; + + GstBuffer *last_buf; + /* dts of last_buf */ + GstClockTime last_dts; + + /* all the atom and chunk book-keeping is delegated here + * unowned/uncounted reference, parent MOOV owns */ + AtomTRAK *trak; +} GstQTPad; + +typedef enum _GstQTMuxState +{ + GST_QT_MUX_STATE_NONE, + GST_QT_MUX_STATE_STARTED, + GST_QT_MUX_STATE_DATA, + GST_QT_MUX_STATE_EOS +} GstQTMuxState; + +struct _GstQTMux +{ + GstElement element; + + GstPad *srcpad; + GstCollectPads *collect; + + /* state */ + GstQTMuxState state; + + /* size of header (prefix, atoms (ftyp, mdat)) */ + 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) */ + guint64 mdat_pos; + + /* atom helper objects */ + AtomsContext *context; + AtomFTYP *ftyp; + AtomMOOV *moov; + + /* fast start */ + FILE *fast_start_file; + + GstTagList *tags; + + /* properties */ + guint32 timescale; + AtomsTreeFlavor flavor; + gboolean fast_start; + gboolean large_file; + gboolean do_ctts; + gchar *fast_start_file_path; + + /* for collect pads event handling function */ + GstPadEventFunction collect_event; +}; + +struct _GstQTMuxClass +{ + GstElementClass parent_class; + + GstQTMuxFormat format; +}; + +/* type register helper struct */ +typedef struct _GstQTMuxClassParams +{ + GstQTMuxFormatProp *prop; + GstCaps *src_caps; + GstCaps *video_sink_caps; + GstCaps *audio_sink_caps; +} GstQTMuxClassParams; + +#define GST_QT_MUX_PARAMS_QDATA g_quark_from_static_string("qt-mux-params") + +GType gst_qt_mux_get_type (void); + +G_END_DECLS + +#endif /* __GST_QT_MUX_H__ */ diff --git a/gst/quicktime/gstqtmuxmap.c b/gst/quicktime/gstqtmuxmap.c new file mode 100644 index 000000000..ca62cc07e --- /dev/null +++ b/gst/quicktime/gstqtmuxmap.c @@ -0,0 +1,245 @@ +/* Quicktime muxer plugin for GStreamer + * Copyright (C) 2008 Thiago Sousa Santos + * Copyright (C) 2008 Mark Nauwelaerts + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "gstqtmuxmap.h" +#include "fourcc.h" +#include "ftypcc.h" + +/* static info related to various format */ + +#define COMMON_VIDEO_CAPS \ + "width = (int) [ 16, 4096 ], " \ + "height = (int) [ 16, 4096 ], " \ + "framerate = (fraction) [ 0, MAX ]" + +#define COMMON_VIDEO_CAPS_NO_FRAMERATE \ + "width = (int) [ 16, 4096 ], " \ + "height = (int) [ 16, 4096 ] " + +#define H264_CAPS \ + "video/x-h264, " \ + COMMON_VIDEO_CAPS + +#define MPEG4V_CAPS \ + "video/mpeg, " \ + "mpegversion = (int) 4, "\ + "systemstream = (boolean) false, " \ + COMMON_VIDEO_CAPS "; " \ + "video/x-divx, " \ + "divxversion = (int) 5, "\ + COMMON_VIDEO_CAPS + +#define COMMON_AUDIO_CAPS(c, r) \ + "channels = (int) [ 1, " G_STRINGIFY (c) " ], " \ + "rate = (int) [ 1, " G_STRINGIFY (r) " ]" + +#define PCM_CAPS \ + "audio/x-raw-int, " \ + "width = (int) 8, " \ + "depth = (int) 8, " \ + COMMON_AUDIO_CAPS (2, MAX) ", " \ + "signed = (boolean) { true, false }; " \ + "audio/x-raw-int, " \ + "width = (int) 16, " \ + "depth = (int) 16, " \ + "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, " \ + COMMON_AUDIO_CAPS (2, MAX) ", " \ + "signed = (boolean) true " \ + +#define PCM_CAPS_FULL \ + PCM_CAPS "; " \ + "audio/x-raw-int, " \ + "width = (int) 24, " \ + "depth = (int) 24, " \ + "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, " \ + COMMON_AUDIO_CAPS (2, MAX) ", " \ + "signed = (boolean) true; " \ + "audio/x-raw-int, " \ + "width = (int) 32, " \ + "depth = (int) 32, " \ + "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, " \ + COMMON_AUDIO_CAPS (2, MAX) ", " \ + "signed = (boolean) true " + +#define MP3_CAPS \ + "audio/mpeg, " \ + "mpegversion = (int) 1, " \ + "layer = (int) 3, " \ + COMMON_AUDIO_CAPS (2, MAX) + +#define AAC_CAPS \ + "audio/mpeg, " \ + "mpegversion = (int) 4, " \ + COMMON_AUDIO_CAPS (8, MAX) + + +GstQTMuxFormatProp gst_qt_mux_format_list[] = { + /* original QuickTime format; see Apple site (e.g. qtff.pdf) */ + { + GST_QT_MUX_FORMAT_QT, + "qtmux", + "QuickTime", + "GstQTMux", + GST_STATIC_CAPS ("video/quicktime"), + GST_STATIC_CAPS ("video/x-raw-rgb, " + COMMON_VIDEO_CAPS "; " + "video/x-raw-yuv, " + "format = (fourcc) UYVY, " + COMMON_VIDEO_CAPS "; " + "video/x-h263, " + "h263version = (string) h263, " + COMMON_VIDEO_CAPS "; " + MPEG4V_CAPS "; " + H264_CAPS "; " + "video/x-dv, " + "systemstream = (boolean) false, " + COMMON_VIDEO_CAPS "; " + "image/jpeg, " + COMMON_VIDEO_CAPS_NO_FRAMERATE "; " "video/x-qt-part"), + GST_STATIC_CAPS (PCM_CAPS_FULL "; " + MP3_CAPS " ; " + AAC_CAPS " ; " + "audio/x-alaw, " + COMMON_AUDIO_CAPS (2, MAX) "; " + "audio/x-mulaw, " COMMON_AUDIO_CAPS (2, MAX)) + } + , + /* ISO 14496-14: mp42 as ISO base media extension + * (supersedes original ISO 144996-1 mp41) */ + { + GST_QT_MUX_FORMAT_MP4, + "mp4mux", + "MP4", + "GstMP4Mux", + /* FIXME does not feel right, due to qt caps mess */ + GST_STATIC_CAPS ("video/quicktime"), + GST_STATIC_CAPS (MPEG4V_CAPS "; " H264_CAPS), + GST_STATIC_CAPS (MP3_CAPS "; " AAC_CAPS) + } + , + /* 3GPP Technical Specification 26.244 V7.3.0 + * (extended in 3GPP2 File Formats for Multimedia Services) */ + { + GST_QT_MUX_FORMAT_3GP, + "gppmux", + "3GPP", + "GstGPPMux", + GST_STATIC_CAPS ("application/x-3gp"), + GST_STATIC_CAPS (H264_CAPS), + GST_STATIC_CAPS ("audio/AMR, " + COMMON_AUDIO_CAPS (8, MAX) "; " MP3_CAPS "; " AAC_CAPS) + } + , + /* ISO 15444-3: Motion-JPEG-2000 (also ISO base media extension) */ + { + GST_QT_MUX_FORMAT_MJ2, + "mj2mux", + "MJ2", + "GstMJ2Mux", + GST_STATIC_CAPS ("video/mj2"), + GST_STATIC_CAPS ("image/x-j2c, " COMMON_VIDEO_CAPS), + GST_STATIC_CAPS (PCM_CAPS) + } + , + { + GST_QT_MUX_FORMAT_NONE, + } + , +}; + +/* pretty static, but may turn out needed a few times */ +AtomsTreeFlavor +gst_qt_mux_map_format_to_flavor (GstQTMuxFormat format) +{ + if (format == GST_QT_MUX_FORMAT_QT) + return ATOMS_TREE_FLAVOR_MOV; + else + return ATOMS_TREE_FLAVOR_ISOM; +} + +/* pretty static, but possibly dynamic format info */ + +/* notes: + * - avc1 brand is not used, since the specific extensions indicated by it + * are not used (e.g. sample groupings, etc) + * - 3GPP2 specific formats not (yet) used, only 3GPP, so no need yet either + * for 3g2a (but later on, moov might be used to conditionally switch to + * 3g2a if needed) */ +void +gst_qt_mux_map_format_to_header (GstQTMuxFormat format, GstBuffer ** _prefix, + guint32 * _major, guint32 * _version, GList ** _compatible, AtomMOOV * moov) +{ + static guint32 qt_brands[] = { 0 }; + static guint32 mp4_brands[] = { FOURCC_mp41, FOURCC_isom, FOURCC_iso2, 0 }; + static guint32 gpp_brands[] = { FOURCC_isom, FOURCC_iso2, 0 }; + static guint32 mjp2_brands[] = { FOURCC_isom, FOURCC_iso2, 0 }; + static guint8 mjp2_prefix[] = + { 0, 0, 0, 12, 'j', 'P', ' ', ' ', 0x0D, 0x0A, 0x87, 0x0A }; + guint32 *comp = NULL; + guint32 major = 0, version; + GstBuffer *prefix = NULL; + GList *result = NULL; + + g_return_if_fail (_prefix != NULL); + g_return_if_fail (_major != NULL); + g_return_if_fail (_version != NULL); + g_return_if_fail (_compatible != NULL); + + version = 1; + switch (format) { + case GST_QT_MUX_FORMAT_QT: + major = FOURCC_qt__; + comp = qt_brands; + version = 0x20050300; + break; + case GST_QT_MUX_FORMAT_MP4: + major = FOURCC_mp42; + comp = mp4_brands; + break; + case GST_QT_MUX_FORMAT_3GP: + major = FOURCC_3gg7; + comp = gpp_brands; + break; + case GST_QT_MUX_FORMAT_MJ2: + major = FOURCC_mjp2; + comp = mjp2_brands; + prefix = gst_buffer_new_and_alloc (sizeof (mjp2_prefix)); + memcpy (GST_BUFFER_DATA (prefix), mjp2_prefix, GST_BUFFER_SIZE (prefix)); + break; + default: + g_assert_not_reached (); + break; + } + + /* convert list to list, hm */ + while (comp && *comp != 0) { + /* order matters over efficiency */ + result = g_list_append (result, GUINT_TO_POINTER (*comp)); + comp++; + } + + *_major = major; + *_version = version; + *_prefix = prefix; + *_compatible = result; + + /* TODO 3GPP may include mp42 as compatible if applicable */ + /* TODO 3GPP major brand 3gp7 if at most 1 video and audio track */ +} diff --git a/gst/quicktime/gstqtmuxmap.h b/gst/quicktime/gstqtmuxmap.h new file mode 100644 index 000000000..7228774f4 --- /dev/null +++ b/gst/quicktime/gstqtmuxmap.h @@ -0,0 +1,57 @@ +/* Quicktime muxer plugin for GStreamer + * Copyright (C) 2008 Thiago Sousa Santos + * Copyright (C) 2008 Mark Nauwelaerts + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_QT_MUX_MAP_H__ +#define __GST_QT_MUX_MAP_H__ + +#include "atoms.h" + +#include +#include + +typedef enum _GstQTMuxFormat +{ + GST_QT_MUX_FORMAT_NONE = 0, + GST_QT_MUX_FORMAT_QT, + GST_QT_MUX_FORMAT_MP4, + GST_QT_MUX_FORMAT_3GP, + GST_QT_MUX_FORMAT_MJ2 +} GstQTMuxFormat; + +typedef struct _GstQTMuxFormatProp +{ + GstQTMuxFormat format; + gchar *name; + gchar *long_name; + gchar *type_name; + GstStaticCaps src_caps; + GstStaticCaps video_sink_caps; + GstStaticCaps audio_sink_caps; +} GstQTMuxFormatProp; + +extern GstQTMuxFormatProp gst_qt_mux_format_list[]; + +void gst_qt_mux_map_format_to_header (GstQTMuxFormat format, GstBuffer ** _prefix, + guint32 * _major, guint32 * verson, + GList ** _compatible, AtomMOOV * moov); + +AtomsTreeFlavor gst_qt_mux_map_format_to_flavor (GstQTMuxFormat format); + +#endif /* __GST_QT_MUX_MAP_H__ */ diff --git a/gst/quicktime/properties.c b/gst/quicktime/properties.c new file mode 100644 index 000000000..48a960a63 --- /dev/null +++ b/gst/quicktime/properties.c @@ -0,0 +1,183 @@ +/* Quicktime muxer plugin for GStreamer + * Copyright (C) 2008 Thiago Sousa Santos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "properties.h" + +/* if needed, re-allocate buffer to ensure size bytes can be written into it + * at offset */ +void +prop_copy_ensure_buffer (guint8 ** buffer, guint64 * bsize, guint64 * offset, + guint64 size) +{ + if (buffer && *bsize - *offset < size) { + *bsize += size + 10 * 1024; + *buffer = g_realloc (*buffer, *bsize); + } +} + +static guint64 +copy_func (void *prop, guint size, guint8 ** buffer, guint64 * bsize, + guint64 * offset) +{ + if (buffer) { + prop_copy_ensure_buffer (buffer, bsize, offset, size); + memcpy (*buffer + *offset, prop, size); + } + *offset += size; + return size; +} + +#define INT_ARRAY_COPY_FUNC_FAST(name, datatype) \ +guint64 prop_copy_ ## name ## _array (datatype *prop, guint size, \ + guint8 ** buffer, guint64 * bsize, guint64 * offset) { \ + return copy_func (prop, sizeof (datatype) * size, buffer, bsize, offset);\ +} + +#define INT_ARRAY_COPY_FUNC(name, datatype) \ +guint64 prop_copy_ ## name ## _array (datatype *prop, guint size, \ + guint8 ** buffer, guint64 * bsize, guint64 * offset) { \ + guint i; \ + \ + for (i = 0; i < size; i++) { \ + prop_copy_ ## name (prop[i], buffer, bsize, offset); \ + } \ + return sizeof (datatype) * size; \ +} + +/* INTEGERS */ +guint64 +prop_copy_uint8 (guint8 prop, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + return copy_func (&prop, sizeof (guint8), buffer, size, offset); +} + +guint64 +prop_copy_uint16 (guint16 prop, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + prop = GUINT16_TO_BE (prop); + return copy_func (&prop, sizeof (guint16), buffer, size, offset); +} + +guint64 +prop_copy_uint32 (guint32 prop, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + prop = GUINT32_TO_BE (prop); + return copy_func (&prop, sizeof (guint32), buffer, size, offset); +} + +guint64 +prop_copy_uint64 (guint64 prop, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + prop = GUINT64_TO_BE (prop); + return copy_func (&prop, sizeof (guint64), buffer, size, offset); +} + +guint64 +prop_copy_int32 (gint32 prop, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + prop = GINT32_TO_BE (prop); + return copy_func (&prop, sizeof (guint32), buffer, size, offset); +} + +/* uint8 can use direct copy in any case, and may be used for large quantity */ +INT_ARRAY_COPY_FUNC_FAST (uint8, guint8); +/* not used in large quantity anyway */ +INT_ARRAY_COPY_FUNC (uint16, guint16); +INT_ARRAY_COPY_FUNC (uint32, guint32); +INT_ARRAY_COPY_FUNC (uint64, guint64); + +/* FOURCC */ +guint64 +prop_copy_fourcc (guint32 prop, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + prop = GINT32_TO_LE (prop); + return copy_func (&prop, sizeof (guint32), buffer, size, offset); +} + +INT_ARRAY_COPY_FUNC (fourcc, guint32); + +/** + * Copies a string of bytes without placing its size at the beginning. + * + * @string: the string to be copied + * @str_size: size of the string + * @buffer: the array to copy the string to + * @offset: the position in the buffer array. + * This value is updated to the point right after the copied string. + * + * Returns: the number of bytes copied + */ +guint64 +prop_copy_fixed_size_string (guint8 * string, guint str_size, guint8 ** buffer, + guint64 * size, guint64 * offset) +{ + return copy_func (string, str_size * sizeof (guint8), buffer, size, offset); +} + +/** + * Copies a string and its size to an array. Example: + * string = 'abc\0' + * result in the array: [3][a][b][c] (each [x] represents a position) + * + * @string: the string to be copied + * @str_size: size of the string + * @buffer: the array to copy the string to + * @offset: the position in the buffer array. + * This value is updated to the point right after the copied string. + * + * Returns: the number of bytes copied + */ +guint64 +prop_copy_size_string (guint8 * string, guint str_size, guint8 ** buffer, + guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + + prop_copy_uint8 (str_size, buffer, size, offset); + prop_copy_fixed_size_string (string, str_size, buffer, size, offset); + return *offset - original_offset; +} + +/** + * Copies a string including its null terminating char to an array. + * + * @string: the string to be copied + * @buffer: the array to copy the string to + * @offset: the position in the buffer array. + * This value is updated to the point right after the copied string. + * + * Returns: the number of bytes copied + */ +guint64 +prop_copy_null_terminated_string (gchar * string, guint8 ** buffer, + guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + guint len = strlen (string); + + prop_copy_fixed_size_string ((guint8 *) string, len, buffer, size, offset); + prop_copy_uint8 ('\0', buffer, size, offset); + return *offset - original_offset; +} diff --git a/gst/quicktime/properties.h b/gst/quicktime/properties.h new file mode 100644 index 000000000..a2935caf0 --- /dev/null +++ b/gst/quicktime/properties.h @@ -0,0 +1,64 @@ +/* Quicktime muxer plugin for GStreamer + * Copyright (C) 2008 Thiago Sousa Santos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __PROPERTIES_H__ +#define __PROPERTIES_H__ + +#include +#include + +/** + * Functions for copying atoms properties. + * + * All of them receive, as the input, the property to be copied, the destination + * buffer, and a pointer to an offset in the destination buffer to copy to the right place. + * This offset will be updated to the new value (offset + copied_size) + * The functions return the size of the property that has been copied or 0 + * if it couldn't copy. + */ + +void prop_copy_ensure_buffer (guint8 ** buffer, guint64 * bsize, guint64 * offset, guint64 size); + +guint64 prop_copy_uint8 (guint8 prop, guint8 **buffer, guint64 *size, guint64 *offset); +guint64 prop_copy_uint16 (guint16 prop, guint8 **buffer, guint64 *size, guint64 *offset); +guint64 prop_copy_uint32 (guint32 prop, guint8 **buffer, guint64 *size, guint64 *offset); +guint64 prop_copy_uint64 (guint64 prop, guint8 **buffer, guint64 *size, guint64 *offset); + +guint64 prop_copy_int32 (gint32 prop, guint8 **buffer, guint64 *size, guint64 *offset); + +guint64 prop_copy_uint8_array (guint8 *prop, guint size, + guint8 **buffer, guint64 *bsize, guint64 *offset); +guint64 prop_copy_uint16_array (guint16 *prop, guint size, + guint8 **buffer, guint64 *bsize, guint64 *offset); +guint64 prop_copy_uint32_array (guint32 *prop, guint size, + guint8 **buffer, guint64 *bsize, guint64 *offset); +guint64 prop_copy_uint64_array (guint64 *prop, guint size, + guint8 **buffer, guint64 *bsize, guint64 *offset); + +guint64 prop_copy_fourcc (guint32 prop, guint8 **buffer, guint64 *size, guint64 *offset); +guint64 prop_copy_fourcc_array (guint32 *prop, guint size, + guint8 **buffer, guint64 *bsize, guint64 *offset); +guint64 prop_copy_fixed_size_string (guint8 *string, guint str_size, + guint8 **buffer, guint64 *size, guint64 *offset); +guint64 prop_copy_size_string (guint8 *string, guint str_size, + guint8 **buffer, guint64 *size, guint64 *offset); +guint64 prop_copy_null_terminated_string (gchar *string, + guint8 **buffer, guint64 *size, guint64 *offset); + +#endif /* __PROPERTIES_H__ */