gst_plugins_good/gst/qtmux/gstqtmux.c
branchRCL_3
changeset 30 7e817e7e631c
parent 29 567bb019e3e3
equal deleted inserted replaced
29:567bb019e3e3 30:7e817e7e631c
     1 /* Quicktime muxer plugin for GStreamer
       
     2  * Copyright (C) 2008 Thiago Sousa Santos <thiagoss@embedded.ufcg.edu.br>
       
     3  * Copyright (C) 2008 Mark Nauwelaerts <mnauw@users.sf.net>
       
     4  *
       
     5  * This library is free software; you can redistribute it and/or
       
     6  * modify it under the terms of the GNU Library General Public
       
     7  * License as published by the Free Software Foundation; either
       
     8  * version 2 of the License, or (at your option) any later version.
       
     9  *
       
    10  * This library is distributed in the hope that it will be useful,
       
    11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
       
    13  * Library General Public License for more details.
       
    14  *
       
    15  * You should have received a copy of the GNU Library General Public
       
    16  * License along with this library; if not, write to the
       
    17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
       
    18  * Boston, MA 02111-1307, USA.
       
    19  */
       
    20 /*
       
    21  * Unless otherwise indicated, Source Code is licensed under MIT license.
       
    22  * See further explanation attached in License Statement (distributed in the file
       
    23  * LICENSE).
       
    24  *
       
    25  * Permission is hereby granted, free of charge, to any person obtaining a copy of
       
    26  * this software and associated documentation files (the "Software"), to deal in
       
    27  * the Software without restriction, including without limitation the rights to
       
    28  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
       
    29  * of the Software, and to permit persons to whom the Software is furnished to do
       
    30  * so, subject to the following conditions:
       
    31  *
       
    32  * The above copyright notice and this permission notice shall be included in all
       
    33  * copies or substantial portions of the Software.
       
    34  *
       
    35  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       
    36  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       
    37  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       
    38  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       
    39  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       
    40  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       
    41  * SOFTWARE.
       
    42  */
       
    43 
       
    44 
       
    45 /**
       
    46  * SECTION:gstqtmux
       
    47  * @short_description: Muxer for quicktime(.mov) files
       
    48  *
       
    49  * <refsect2>
       
    50  * <para>
       
    51  * This element merges streams (audio and video) into qt(.mov) files.
       
    52  * </para>
       
    53  * <title>Example pipelines</title>
       
    54  * <para>
       
    55  * <programlisting>
       
    56  * gst-launch v4l2src num-buffers=500 ! video/x-raw-yuv,width=320,height=240 ! ffmpegcolorspace ! qtmux ! filesink location=video.mov
       
    57  * </programlisting>
       
    58  * Records a video stream captured from a v4l2 device and muxes it into a qt file.
       
    59  * </para>
       
    60  * </refsect2>
       
    61  *
       
    62  * Last reviewed on 2008-08-27
       
    63  */
       
    64 
       
    65 /*
       
    66  * Based on avimux
       
    67  */
       
    68 
       
    69 #ifdef HAVE_CONFIG_H
       
    70 #include "config.h"
       
    71 #endif
       
    72 
       
    73 #include <glib/gstdio.h>
       
    74 
       
    75 #include <gst/gst.h>
       
    76 #include <gst/base/gstcollectpads.h>
       
    77 
       
    78 #include <sys/types.h>
       
    79 #ifdef G_OS_WIN32
       
    80 #include <io.h>                 /* lseek, open, close, read */
       
    81 #undef lseek
       
    82 #define lseek _lseeki64
       
    83 #undef off_t
       
    84 #define off_t guint64
       
    85 #endif
       
    86 
       
    87 #ifdef HAVE_UNISTD_H
       
    88 #  include <unistd.h>
       
    89 #endif
       
    90 
       
    91 #include "gstqtmux.h"
       
    92 
       
    93 GST_DEBUG_CATEGORY_STATIC (gst_qt_mux_debug);
       
    94 #define GST_CAT_DEFAULT gst_qt_mux_debug
       
    95 
       
    96 /* QTMux signals and args */
       
    97 enum
       
    98 {
       
    99   /* FILL ME */
       
   100   LAST_SIGNAL
       
   101 };
       
   102 
       
   103 enum
       
   104 {
       
   105   PROP_0,
       
   106   PROP_LARGE_FILE,
       
   107   PROP_MOVIE_TIMESCALE,
       
   108   PROP_DO_CTTS,
       
   109   PROP_FLAVOR,
       
   110   PROP_FAST_START,
       
   111   PROP_FAST_START_TEMP_FILE
       
   112 };
       
   113 
       
   114 /* some spare for header size as well */
       
   115 #define MDAT_LARGE_FILE_LIMIT           ((guint64) 1024 * 1024 * 1024 * 2)
       
   116 
       
   117 #define DEFAULT_LARGE_FILE              FALSE
       
   118 #define DEFAULT_MOVIE_TIMESCALE         1000
       
   119 #define DEFAULT_DO_CTTS                 FALSE
       
   120 #define DEFAULT_FAST_START              FALSE
       
   121 #define DEFAULT_FAST_START_TEMP_FILE    NULL
       
   122 
       
   123 static void gst_qt_mux_finalize (GObject * object);
       
   124 
       
   125 static GstStateChangeReturn gst_qt_mux_change_state (GstElement * element,
       
   126     GstStateChange transition);
       
   127 
       
   128 /* property functions */
       
   129 static void gst_qt_mux_set_property (GObject * object,
       
   130     guint prop_id, const GValue * value, GParamSpec * pspec);
       
   131 static void gst_qt_mux_get_property (GObject * object,
       
   132     guint prop_id, GValue * value, GParamSpec * pspec);
       
   133 
       
   134 /* pad functions */
       
   135 static GstPad *gst_qt_mux_request_new_pad (GstElement * element,
       
   136     GstPadTemplate * templ, const gchar * name);
       
   137 static void gst_qt_mux_release_pad (GstElement * element, GstPad * pad);
       
   138 
       
   139 /* event */
       
   140 static gboolean gst_qt_mux_sink_event (GstPad * pad, GstEvent * event);
       
   141 
       
   142 static GstFlowReturn gst_qt_mux_collected (GstCollectPads * pads,
       
   143     gpointer user_data);
       
   144 static GstFlowReturn gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad,
       
   145     GstBuffer * buf);
       
   146 
       
   147 static GstElementClass *parent_class = NULL;
       
   148 
       
   149 static void
       
   150 gst_qt_mux_base_init (gpointer g_class)
       
   151 {
       
   152   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
       
   153   GstQTMuxClass *klass = (GstQTMuxClass *) g_class;
       
   154   GstQTMuxClassParams *params;
       
   155   GstElementDetails details;
       
   156   GstPadTemplate *videosinktempl, *audiosinktempl, *srctempl;
       
   157 
       
   158   params =
       
   159       (GstQTMuxClassParams *) g_type_get_qdata (G_OBJECT_CLASS_TYPE (g_class),
       
   160       GST_QT_MUX_PARAMS_QDATA);
       
   161   g_assert (params != NULL);
       
   162 
       
   163   /* construct the element details struct */
       
   164   details.longname = g_strdup_printf ("%s Muxer", params->prop->long_name);
       
   165   details.klass = g_strdup ("Codec/Muxer");
       
   166   details.description =
       
   167       g_strdup_printf ("Multiplex audio and video into a %s file",
       
   168       params->prop->long_name);
       
   169   details.author = "Thiago Sousa Santos <thiagoss@embedded.ufcg.edu.br>";
       
   170   gst_element_class_set_details (element_class, &details);
       
   171   g_free (details.longname);
       
   172   g_free (details.klass);
       
   173   g_free (details.description);
       
   174 
       
   175   /* pad templates */
       
   176   srctempl = gst_pad_template_new ("src", GST_PAD_SRC,
       
   177       GST_PAD_ALWAYS, params->src_caps);
       
   178   gst_element_class_add_pad_template (element_class, srctempl);
       
   179 
       
   180   if (params->audio_sink_caps) {
       
   181     audiosinktempl = gst_pad_template_new ("audio_%d",
       
   182         GST_PAD_SINK, GST_PAD_REQUEST, params->audio_sink_caps);
       
   183     gst_element_class_add_pad_template (element_class, audiosinktempl);
       
   184   }
       
   185 
       
   186   if (params->video_sink_caps) {
       
   187     videosinktempl = gst_pad_template_new ("video_%d",
       
   188         GST_PAD_SINK, GST_PAD_REQUEST, params->video_sink_caps);
       
   189     gst_element_class_add_pad_template (element_class, videosinktempl);
       
   190   }
       
   191 
       
   192   klass->format = params->prop->format;
       
   193 }
       
   194 
       
   195 static void
       
   196 gst_qt_mux_class_init (GstQTMuxClass * klass)
       
   197 {
       
   198   GObjectClass *gobject_class;
       
   199   GstElementClass *gstelement_class;
       
   200 
       
   201   gobject_class = (GObjectClass *) klass;
       
   202   gstelement_class = (GstElementClass *) klass;
       
   203 
       
   204   parent_class = g_type_class_peek_parent (klass);
       
   205 
       
   206   gobject_class->finalize = gst_qt_mux_finalize;
       
   207   gobject_class->get_property = gst_qt_mux_get_property;
       
   208   gobject_class->set_property = gst_qt_mux_set_property;
       
   209 
       
   210   g_object_class_install_property (gobject_class, PROP_LARGE_FILE,
       
   211       g_param_spec_boolean ("large-file", "Support for large files",
       
   212           "Uses 64bits to some fields instead of 32bits, "
       
   213           "providing support for large files",
       
   214           DEFAULT_LARGE_FILE, G_PARAM_READWRITE));
       
   215   g_object_class_install_property (gobject_class, PROP_MOVIE_TIMESCALE,
       
   216       g_param_spec_uint ("movie-timescale", "Movie timescale",
       
   217           "Timescale to use in the movie (units per second)",
       
   218           1, G_MAXUINT32, DEFAULT_MOVIE_TIMESCALE,
       
   219           G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
       
   220   g_object_class_install_property (gobject_class, PROP_DO_CTTS,
       
   221       g_param_spec_boolean ("presentation-time",
       
   222           "Include presentation-time info",
       
   223           "Calculate and include presentation/composition time (in addition to decoding time)"
       
   224           " (use with caution)", DEFAULT_DO_CTTS,
       
   225           G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
       
   226   g_object_class_install_property (gobject_class, PROP_FAST_START,
       
   227       g_param_spec_boolean ("faststart", "Format file to faststart",
       
   228           "If the file should be formated for faststart (headers first). ",
       
   229           DEFAULT_FAST_START, G_PARAM_READWRITE));
       
   230   g_object_class_install_property (gobject_class, PROP_FAST_START_TEMP_FILE,
       
   231       g_param_spec_string ("faststart-file", "File to use for storing buffers",
       
   232           "File that will be used temporarily to store data from the stream when "
       
   233           "creating a faststart file. If null a filepath will be created automatically",
       
   234           DEFAULT_FAST_START_TEMP_FILE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
       
   235 
       
   236   gstelement_class->request_new_pad =
       
   237       GST_DEBUG_FUNCPTR (gst_qt_mux_request_new_pad);
       
   238   gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_qt_mux_change_state);
       
   239   gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_qt_mux_release_pad);
       
   240 }
       
   241 
       
   242 static void
       
   243 gst_qt_mux_pad_reset (GstQTPad * qtpad)
       
   244 {
       
   245   qtpad->fourcc = 0;
       
   246   qtpad->is_out_of_order = FALSE;
       
   247   qtpad->have_dts = FALSE;
       
   248   qtpad->sample_size = 0;
       
   249   qtpad->sync = FALSE;
       
   250   qtpad->last_dts = 0;
       
   251 
       
   252   if (qtpad->last_buf)
       
   253     gst_buffer_replace (&qtpad->last_buf, NULL);
       
   254 
       
   255   /* reference owned elsewhere */
       
   256   qtpad->trak = NULL;
       
   257 }
       
   258 
       
   259 /*
       
   260  * Takes GstQTMux back to its initial state
       
   261  */
       
   262 static void
       
   263 gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc)
       
   264 {
       
   265   GSList *walk;
       
   266 
       
   267   qtmux->state = GST_QT_MUX_STATE_NONE;
       
   268   qtmux->header_size = 0;
       
   269   qtmux->mdat_size = 0;
       
   270   qtmux->mdat_pos = 0;
       
   271   qtmux->longest_chunk = GST_CLOCK_TIME_NONE;
       
   272 
       
   273   if (qtmux->ftyp) {
       
   274     atom_ftyp_free (qtmux->ftyp);
       
   275     qtmux->ftyp = NULL;
       
   276   }
       
   277   if (qtmux->moov) {
       
   278     atom_moov_free (qtmux->moov);
       
   279     qtmux->moov = NULL;
       
   280   }
       
   281   if (qtmux->fast_start_file) {
       
   282     fclose (qtmux->fast_start_file);
       
   283     qtmux->fast_start_file = NULL;
       
   284   }
       
   285   gst_tag_setter_reset_tags (GST_TAG_SETTER (qtmux));
       
   286 
       
   287   /* reset pad data */
       
   288   for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
       
   289     GstQTPad *qtpad = (GstQTPad *) walk->data;
       
   290     gst_qt_mux_pad_reset (qtpad);
       
   291 
       
   292     /* hm, moov_free above yanked the traks away from us,
       
   293      * so do not free, but do clear */
       
   294     qtpad->trak = NULL;
       
   295   }
       
   296 
       
   297   if (alloc) {
       
   298     qtmux->moov = atom_moov_new (qtmux->context);
       
   299     /* ensure all is as nice and fresh as request_new_pad would provide it */
       
   300     for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
       
   301       GstQTPad *qtpad = (GstQTPad *) walk->data;
       
   302 
       
   303       qtpad->trak = atom_trak_new (qtmux->context);
       
   304       atom_moov_add_trak (qtmux->moov, qtpad->trak);
       
   305     }
       
   306   }
       
   307 }
       
   308 
       
   309 static void
       
   310 gst_qt_mux_init (GstQTMux * qtmux, GstQTMuxClass * qtmux_klass)
       
   311 {
       
   312   GstElementClass *klass = GST_ELEMENT_CLASS (qtmux_klass);
       
   313   GstPadTemplate *templ;
       
   314   GstCaps *caps;
       
   315 
       
   316   templ = gst_element_class_get_pad_template (klass, "src");
       
   317   qtmux->srcpad = gst_pad_new_from_template (templ, "src");
       
   318   caps = gst_caps_copy (gst_pad_get_pad_template_caps (qtmux->srcpad));
       
   319   gst_pad_set_caps (qtmux->srcpad, caps);
       
   320   gst_caps_unref (caps);
       
   321   gst_pad_use_fixed_caps (qtmux->srcpad);
       
   322   gst_element_add_pad (GST_ELEMENT (qtmux), qtmux->srcpad);
       
   323 
       
   324   qtmux->collect = gst_collect_pads_new ();
       
   325   gst_collect_pads_set_function (qtmux->collect,
       
   326       (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_qt_mux_collected), qtmux);
       
   327 
       
   328   /* properties set to default upon construction */
       
   329 
       
   330   /* always need this */
       
   331   qtmux->context =
       
   332       atoms_context_new (gst_qt_mux_map_format_to_flavor (qtmux_klass->format));
       
   333 
       
   334   /* internals to initial state */
       
   335   gst_qt_mux_reset (qtmux, TRUE);
       
   336 }
       
   337 
       
   338 
       
   339 static void
       
   340 gst_qt_mux_finalize (GObject * object)
       
   341 {
       
   342   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
       
   343 
       
   344   gst_qt_mux_reset (qtmux, FALSE);
       
   345 
       
   346   if (qtmux->fast_start_file_path)
       
   347     g_free (qtmux->fast_start_file_path);
       
   348 
       
   349   atoms_context_free (qtmux->context);
       
   350   gst_object_unref (qtmux->collect);
       
   351 
       
   352   G_OBJECT_CLASS (parent_class)->finalize (object);
       
   353 }
       
   354 
       
   355 static void
       
   356 gst_qt_mux_add_mp4_tag (GstQTMux * qtmux, const GstTagList * list,
       
   357     const char *tag, const char *tag2, guint32 fourcc)
       
   358 {
       
   359   switch (gst_tag_get_type (tag)) {
       
   360       /* strings */
       
   361     case G_TYPE_STRING:
       
   362     {
       
   363       gchar *str = NULL;
       
   364 
       
   365       if (!gst_tag_list_get_string (list, tag, &str) || !str)
       
   366         break;
       
   367       GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
       
   368           GST_FOURCC_ARGS (fourcc), str);
       
   369       atom_moov_add_str_tag (qtmux->moov, fourcc, str);
       
   370       g_free (str);
       
   371       break;
       
   372     }
       
   373       /* double */
       
   374     case G_TYPE_DOUBLE:
       
   375     {
       
   376       gdouble value;
       
   377 
       
   378       if (!gst_tag_list_get_double (list, tag, &value))
       
   379         break;
       
   380       GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u",
       
   381           GST_FOURCC_ARGS (fourcc), (gint) value);
       
   382       atom_moov_add_uint_tag (qtmux->moov, fourcc, 21, (gint) value);
       
   383       break;
       
   384     }
       
   385       /* paired unsigned integers */
       
   386     case G_TYPE_UINT:
       
   387     {
       
   388       guint value;
       
   389       guint count;
       
   390 
       
   391       if (!gst_tag_list_get_uint (list, tag, &value) ||
       
   392           !gst_tag_list_get_uint (list, tag2, &count))
       
   393         break;
       
   394       GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u/%u",
       
   395           GST_FOURCC_ARGS (fourcc), value, count);
       
   396       atom_moov_add_uint_tag (qtmux->moov, fourcc, 0,
       
   397           value << 16 | (count & 0xFFFF));
       
   398       break;
       
   399     }
       
   400     default:
       
   401       g_assert_not_reached ();
       
   402       break;
       
   403   }
       
   404 }
       
   405 
       
   406 static void
       
   407 gst_qt_mux_add_mp4_date (GstQTMux * qtmux, const GstTagList * list,
       
   408     const char *tag, const char *tag2, guint32 fourcc)
       
   409 {
       
   410   GDate *date = NULL;
       
   411   GDateYear year;
       
   412   GDateMonth month;
       
   413   GDateDay day;
       
   414   gchar *str;
       
   415 
       
   416   g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_DATE);
       
   417 
       
   418   if (!gst_tag_list_get_date (list, tag, &date) || !date)
       
   419     return;
       
   420 
       
   421   year = g_date_get_year (date);
       
   422   month = g_date_get_month (date);
       
   423   day = g_date_get_day (date);
       
   424 
       
   425   if (year == G_DATE_BAD_YEAR && month == G_DATE_BAD_MONTH &&
       
   426       day == G_DATE_BAD_DAY) {
       
   427     GST_WARNING_OBJECT (qtmux, "invalid date in tag");
       
   428     return;
       
   429   }
       
   430 
       
   431   str = g_strdup_printf ("%u-%u-%u", year, month, day);
       
   432   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
       
   433       GST_FOURCC_ARGS (fourcc), str);
       
   434   atom_moov_add_str_tag (qtmux->moov, fourcc, str);
       
   435 }
       
   436 
       
   437 static void
       
   438 gst_qt_mux_add_mp4_cover (GstQTMux * qtmux, const GstTagList * list,
       
   439     const char *tag, const char *tag2, guint32 fourcc)
       
   440 {
       
   441   GValue value = { 0, };
       
   442   GstBuffer *buf;
       
   443   GstCaps *caps;
       
   444   GstStructure *structure;
       
   445   gint flags = 0;
       
   446 
       
   447   g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_BUFFER);
       
   448 
       
   449   if (!gst_tag_list_copy_value (&value, list, tag))
       
   450     return;
       
   451 
       
   452   buf = gst_value_get_buffer (&value);
       
   453   if (!buf)
       
   454     goto done;
       
   455 
       
   456   caps = gst_buffer_get_caps (buf);
       
   457   if (!caps) {
       
   458     GST_WARNING_OBJECT (qtmux, "preview image without caps");
       
   459     goto done;
       
   460   }
       
   461 
       
   462   GST_DEBUG_OBJECT (qtmux, "preview image caps %" GST_PTR_FORMAT, caps);
       
   463 
       
   464   structure = gst_caps_get_structure (caps, 0);
       
   465   if (gst_structure_has_name (structure, "image/jpeg"))
       
   466     flags = 13;
       
   467   else if (gst_structure_has_name (structure, "image/png"))
       
   468     flags = 14;
       
   469   gst_caps_unref (caps);
       
   470 
       
   471   if (!flags) {
       
   472     GST_WARNING_OBJECT (qtmux, "preview image format not supported");
       
   473     goto done;
       
   474   }
       
   475 
       
   476   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT
       
   477       " -> image size %d", GST_FOURCC_ARGS (fourcc), GST_BUFFER_SIZE (buf));
       
   478   atom_moov_add_tag (qtmux->moov, fourcc, flags, GST_BUFFER_DATA (buf),
       
   479       GST_BUFFER_SIZE (buf));
       
   480 done:
       
   481   g_value_unset (&value);
       
   482 }
       
   483 
       
   484 static void
       
   485 gst_qt_mux_add_3gp_str (GstQTMux * qtmux, const GstTagList * list,
       
   486     const char *tag, const char *tag2, guint32 fourcc)
       
   487 {
       
   488   gchar *str = NULL;
       
   489   guint number;
       
   490 
       
   491   g_return_if_fail (gst_tag_get_type (tag) == G_TYPE_STRING);
       
   492   g_return_if_fail (!tag2 || gst_tag_get_type (tag2) == G_TYPE_UINT);
       
   493 
       
   494   if (!gst_tag_list_get_string (list, tag, &str) || !str)
       
   495     return;
       
   496 
       
   497   if (tag2)
       
   498     if (!gst_tag_list_get_uint (list, tag2, &number))
       
   499       tag2 = NULL;
       
   500 
       
   501   if (!tag2) {
       
   502     GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
       
   503         GST_FOURCC_ARGS (fourcc), str);
       
   504     atom_moov_add_3gp_str_tag (qtmux->moov, fourcc, str);
       
   505   } else {
       
   506     GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s/%d",
       
   507         GST_FOURCC_ARGS (fourcc), str, number);
       
   508     atom_moov_add_3gp_str_int_tag (qtmux->moov, fourcc, str, number);
       
   509   }
       
   510 
       
   511   g_free (str);
       
   512 }
       
   513 
       
   514 static void
       
   515 gst_qt_mux_add_3gp_date (GstQTMux * qtmux, const GstTagList * list,
       
   516     const char *tag, const char *tag2, guint32 fourcc)
       
   517 {
       
   518   GDate *date = NULL;
       
   519   GDateYear year;
       
   520 
       
   521   g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_DATE);
       
   522 
       
   523   if (!gst_tag_list_get_date (list, tag, &date) || !date)
       
   524     return;
       
   525 
       
   526   year = g_date_get_year (date);
       
   527 
       
   528   if (year == G_DATE_BAD_YEAR) {
       
   529     GST_WARNING_OBJECT (qtmux, "invalid date in tag");
       
   530     return;
       
   531   }
       
   532 
       
   533   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %d",
       
   534       GST_FOURCC_ARGS (fourcc), year);
       
   535   atom_moov_add_3gp_uint_tag (qtmux->moov, fourcc, year);
       
   536 }
       
   537 
       
   538 static void
       
   539 gst_qt_mux_add_3gp_location (GstQTMux * qtmux, const GstTagList * list,
       
   540     const char *tag, const char *tag2, guint32 fourcc)
       
   541 {
       
   542   gdouble latitude = -360, longitude = -360, altitude = 0;
       
   543   gchar *location = NULL;
       
   544   guint8 *data, *ddata;
       
   545   gint size = 0, len = 0;
       
   546   gboolean ret = FALSE;
       
   547 
       
   548   g_return_if_fail (strcmp (tag, GST_TAG_GEO_LOCATION_NAME) == 0);
       
   549 
       
   550   ret = gst_tag_list_get_string (list, tag, &location);
       
   551   ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_LONGITUDE,
       
   552       &longitude);
       
   553   ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_LATITUDE,
       
   554       &latitude);
       
   555   ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_ELEVATION,
       
   556       &altitude);
       
   557 
       
   558   if (!ret)
       
   559     return;
       
   560 
       
   561   if (location)
       
   562     len = strlen (location);
       
   563   size += len + 1 + 2;
       
   564 
       
   565   /* role + (long, lat, alt) + body + notes */
       
   566   size += 1 + 3 * 4 + 1 + 1;
       
   567 
       
   568   data = ddata = g_malloc (size);
       
   569 
       
   570   /* language tag */
       
   571   GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE));
       
   572   /* location */
       
   573   if (location)
       
   574     memcpy (data + 2, location, len);
       
   575   GST_WRITE_UINT8 (data + 2 + len, 0);
       
   576   data += len + 1 + 2;
       
   577   /* role */
       
   578   GST_WRITE_UINT8 (data, 0);
       
   579   /* long, lat, alt */
       
   580   GST_WRITE_UINT32_BE (data + 1, (guint32) (longitude * 65536.0));
       
   581   GST_WRITE_UINT32_BE (data + 5, (guint32) (latitude * 65536.0));
       
   582   GST_WRITE_UINT32_BE (data + 9, (guint32) (altitude * 65536.0));
       
   583   /* neither astronomical body nor notes */
       
   584   GST_WRITE_UINT16_BE (data + 13, 0);
       
   585 
       
   586   GST_DEBUG_OBJECT (qtmux, "Adding tag 'loci'");
       
   587   atom_moov_add_3gp_tag (qtmux->moov, fourcc, ddata, size);
       
   588   g_free (ddata);
       
   589 }
       
   590 
       
   591 static void
       
   592 gst_qt_mux_add_3gp_keywords (GstQTMux * qtmux, const GstTagList * list,
       
   593     const char *tag, const char *tag2, guint32 fourcc)
       
   594 {
       
   595   gchar *keywords = NULL;
       
   596   guint8 *data, *ddata;
       
   597   gint size = 0, i;
       
   598   gchar **kwds;
       
   599 
       
   600   g_return_if_fail (strcmp (tag, GST_TAG_KEYWORDS) == 0);
       
   601 
       
   602   if (!gst_tag_list_get_string (list, tag, &keywords) || !keywords)
       
   603     return;
       
   604 
       
   605   kwds = g_strsplit (keywords, ",", 0);
       
   606 
       
   607   size = 0;
       
   608   for (i = 0; kwds[i]; i++) {
       
   609     /* size byte + null-terminator */
       
   610     size += strlen (kwds[i]) + 1 + 1;
       
   611   }
       
   612 
       
   613   /* language tag + count + keywords */
       
   614   size += 2 + 1;
       
   615 
       
   616   data = ddata = g_malloc (size);
       
   617 
       
   618   /* language tag */
       
   619   GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE));
       
   620   /* count */
       
   621   GST_WRITE_UINT8 (data + 2, i);
       
   622   data += 3;
       
   623   /* keywords */
       
   624   for (i = 0; kwds[i]; ++i) {
       
   625     gint len = strlen (kwds[i]);
       
   626 
       
   627     GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
       
   628         GST_FOURCC_ARGS (fourcc), kwds[i]);
       
   629     /* size */
       
   630     GST_WRITE_UINT8 (data, len + 1);
       
   631     memcpy (data + 1, kwds[i], len + 1);
       
   632     data += len + 2;
       
   633   }
       
   634 
       
   635   g_strfreev (kwds);
       
   636 
       
   637   atom_moov_add_3gp_tag (qtmux->moov, fourcc, ddata, size);
       
   638   g_free (ddata);
       
   639 }
       
   640 
       
   641 
       
   642 typedef void (*GstQTMuxAddTagFunc) (GstQTMux * mux, const GstTagList * list,
       
   643     const char *tag, const char *tag2, guint32 fourcc);
       
   644 
       
   645 /*
       
   646  * Struct to record mappings from gstreamer tags to fourcc codes
       
   647  */
       
   648 typedef struct _GstTagToFourcc
       
   649 {
       
   650   guint32 fourcc;
       
   651   const gchar *gsttag;
       
   652   const gchar *gsttag2;
       
   653   const GstQTMuxAddTagFunc func;
       
   654 } GstTagToFourcc;
       
   655 
       
   656 /* tag list tags to fourcc matching */
       
   657 static const GstTagToFourcc tag_matches_mp4[] = {
       
   658   {FOURCC__alb, GST_TAG_ALBUM, NULL, gst_qt_mux_add_mp4_tag},
       
   659   {FOURCC__ART, GST_TAG_ARTIST, NULL, gst_qt_mux_add_mp4_tag},
       
   660   {FOURCC__cmt, GST_TAG_COMMENT, NULL, gst_qt_mux_add_mp4_tag},
       
   661   {FOURCC__wrt, GST_TAG_COMPOSER, NULL, gst_qt_mux_add_mp4_tag},
       
   662   {FOURCC__gen, GST_TAG_GENRE, NULL, gst_qt_mux_add_mp4_tag},
       
   663   {FOURCC__nam, GST_TAG_TITLE, NULL, gst_qt_mux_add_mp4_tag},
       
   664   {FOURCC__des, GST_TAG_DESCRIPTION, NULL, gst_qt_mux_add_mp4_tag},
       
   665   {FOURCC__too, GST_TAG_ENCODER, NULL, gst_qt_mux_add_mp4_tag},
       
   666   {FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, gst_qt_mux_add_mp4_tag},
       
   667   {FOURCC_keyw, GST_TAG_KEYWORDS, NULL, gst_qt_mux_add_mp4_tag},
       
   668   {FOURCC__day, GST_TAG_DATE, NULL, gst_qt_mux_add_mp4_date},
       
   669   {FOURCC_tmpo, GST_TAG_BEATS_PER_MINUTE, NULL, gst_qt_mux_add_mp4_tag},
       
   670   {FOURCC_trkn, GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT,
       
   671       gst_qt_mux_add_mp4_tag},
       
   672   {FOURCC_disk, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT,
       
   673       gst_qt_mux_add_mp4_tag},
       
   674   {FOURCC_covr, GST_TAG_PREVIEW_IMAGE, NULL, gst_qt_mux_add_mp4_cover},
       
   675   {0, NULL,}
       
   676 };
       
   677 
       
   678 static const GstTagToFourcc tag_matches_3gp[] = {
       
   679   {FOURCC_titl, GST_TAG_TITLE, NULL, gst_qt_mux_add_3gp_str},
       
   680   {FOURCC_dscp, GST_TAG_DESCRIPTION, NULL, gst_qt_mux_add_3gp_str},
       
   681   {FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, gst_qt_mux_add_3gp_str},
       
   682   {FOURCC_perf, GST_TAG_ARTIST, NULL, gst_qt_mux_add_3gp_str},
       
   683   {FOURCC_auth, GST_TAG_COMPOSER, NULL, gst_qt_mux_add_3gp_str},
       
   684   {FOURCC_gnre, GST_TAG_GENRE, NULL, gst_qt_mux_add_3gp_str},
       
   685   {FOURCC_kywd, GST_TAG_KEYWORDS, NULL, gst_qt_mux_add_3gp_keywords},
       
   686   {FOURCC_yrrc, GST_TAG_DATE, NULL, gst_qt_mux_add_3gp_date},
       
   687   {FOURCC_albm, GST_TAG_ALBUM, GST_TAG_TRACK_NUMBER, gst_qt_mux_add_3gp_str},
       
   688   {FOURCC_loci, GST_TAG_GEO_LOCATION_NAME, NULL, gst_qt_mux_add_3gp_location},
       
   689   {0, NULL,}
       
   690 };
       
   691 
       
   692 /* qtdemux produces these for atoms it cannot parse */
       
   693 #define GST_QT_DEMUX_PRIVATE_TAG "private-qt-tag"
       
   694 
       
   695 static void
       
   696 gst_qt_mux_add_metadata_tags (GstQTMux * qtmux, const GstTagList * list)
       
   697 {
       
   698   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
       
   699   guint32 fourcc;
       
   700   gint i;
       
   701   const gchar *tag, *tag2;
       
   702   const GstTagToFourcc *tag_matches;
       
   703 
       
   704   switch (qtmux_klass->format) {
       
   705     case GST_QT_MUX_FORMAT_3GP:
       
   706       tag_matches = tag_matches_3gp;
       
   707       break;
       
   708     case GST_QT_MUX_FORMAT_MJ2:
       
   709       tag_matches = NULL;
       
   710       break;
       
   711     default:
       
   712       /* sort of iTunes style for mp4 and QT (?) */
       
   713       tag_matches = tag_matches_mp4;
       
   714       break;
       
   715   }
       
   716 
       
   717   if (!tag_matches)
       
   718     return;
       
   719 
       
   720   for (i = 0; tag_matches[i].fourcc; i++) {
       
   721     fourcc = tag_matches[i].fourcc;
       
   722     tag = tag_matches[i].gsttag;
       
   723     tag2 = tag_matches[i].gsttag2;
       
   724 
       
   725     g_assert (tag_matches[i].func);
       
   726     tag_matches[i].func (qtmux, list, tag, tag2, fourcc);
       
   727   }
       
   728 
       
   729   /* add unparsed blobs if present */
       
   730   if (gst_tag_exists (GST_QT_DEMUX_PRIVATE_TAG)) {
       
   731     guint num_tags;
       
   732 
       
   733     num_tags = gst_tag_list_get_tag_size (list, GST_QT_DEMUX_PRIVATE_TAG);
       
   734     for (i = 0; i < num_tags; ++i) {
       
   735       const GValue *val;
       
   736       GstBuffer *buf;
       
   737       GstCaps *caps = NULL;
       
   738 
       
   739       val = gst_tag_list_get_value_index (list, GST_QT_DEMUX_PRIVATE_TAG, i);
       
   740       buf = (GstBuffer *) gst_value_get_mini_object (val);
       
   741 
       
   742       if (buf && (caps = gst_buffer_get_caps (buf))) {
       
   743         GstStructure *s;
       
   744         const gchar *style = NULL;
       
   745 
       
   746         GST_DEBUG_OBJECT (qtmux, "Found private tag %d/%d; size %d, caps %"
       
   747             GST_PTR_FORMAT, i, num_tags, GST_BUFFER_SIZE (buf), caps);
       
   748         s = gst_caps_get_structure (caps, 0);
       
   749         if (s && (style = gst_structure_get_string (s, "style"))) {
       
   750           /* try to prevent some style tag ending up into another variant
       
   751            * (todo: make into a list if more cases) */
       
   752           if ((strcmp (style, "itunes") == 0 &&
       
   753                   qtmux_klass->format == GST_QT_MUX_FORMAT_MP4) ||
       
   754               (strcmp (style, "iso") == 0 &&
       
   755                   qtmux_klass->format == GST_QT_MUX_FORMAT_3GP)) {
       
   756             GST_DEBUG_OBJECT (qtmux, "Adding private tag");
       
   757             atom_moov_add_blob_tag (qtmux->moov, GST_BUFFER_DATA (buf),
       
   758                 GST_BUFFER_SIZE (buf));
       
   759           }
       
   760         }
       
   761         gst_caps_unref (caps);
       
   762       }
       
   763     }
       
   764   }
       
   765 
       
   766   return;
       
   767 }
       
   768 
       
   769 /*
       
   770  * Gets the tagsetter iface taglist and puts the known tags
       
   771  * into the output stream
       
   772  */
       
   773 static void
       
   774 gst_qt_mux_setup_metadata (GstQTMux * qtmux)
       
   775 {
       
   776   const GstTagList *tags;
       
   777 
       
   778   tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (qtmux));
       
   779 
       
   780   GST_LOG_OBJECT (qtmux, "tags: %" GST_PTR_FORMAT, tags);
       
   781 
       
   782   if (tags && !gst_tag_list_is_empty (tags)) {
       
   783     GST_DEBUG_OBJECT (qtmux, "Formatting tags");
       
   784     gst_qt_mux_add_metadata_tags (qtmux, tags);
       
   785   } else {
       
   786     GST_DEBUG_OBJECT (qtmux, "No tags received");
       
   787   }
       
   788 }
       
   789 
       
   790 static GstFlowReturn
       
   791 gst_qt_mux_send_buffer (GstQTMux * qtmux, GstBuffer * buf, guint64 * offset,
       
   792     gboolean mind_fast)
       
   793 {
       
   794   GstFlowReturn res;
       
   795   guint8 *data;
       
   796   guint size;
       
   797 
       
   798   g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR);
       
   799 
       
   800   data = GST_BUFFER_DATA (buf);
       
   801   size = GST_BUFFER_SIZE (buf);
       
   802 
       
   803   GST_LOG_OBJECT (qtmux, "sending buffer size %d", size);
       
   804 
       
   805   if (mind_fast && qtmux->fast_start_file) {
       
   806     gint ret;
       
   807 
       
   808     GST_LOG_OBJECT (qtmux, "to temporary file");
       
   809     ret = fwrite (data, sizeof (guint8), size, qtmux->fast_start_file);
       
   810     gst_buffer_unref (buf);
       
   811     if (ret != size)
       
   812       goto write_error;
       
   813     else
       
   814       res = GST_FLOW_OK;
       
   815   } else {
       
   816     GST_LOG_OBJECT (qtmux, "downstream");
       
   817 
       
   818     gst_buffer_set_caps (buf, GST_PAD_CAPS (qtmux->srcpad));
       
   819     res = gst_pad_push (qtmux->srcpad, buf);
       
   820   }
       
   821 
       
   822   if (G_LIKELY (offset))
       
   823     *offset += size;
       
   824 
       
   825   return res;
       
   826 
       
   827   /* ERRORS */
       
   828 write_error:
       
   829   {
       
   830     GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
       
   831         ("Failed to write to temporary file"), GST_ERROR_SYSTEM);
       
   832     return GST_FLOW_ERROR;
       
   833   }
       
   834 }
       
   835 
       
   836 static GstFlowReturn
       
   837 gst_qt_mux_send_buffered_data (GstQTMux * qtmux, guint64 * offset)
       
   838 {
       
   839   GstFlowReturn ret = GST_FLOW_OK;
       
   840   GstBuffer *buf = NULL;
       
   841 
       
   842   if (fflush (qtmux->fast_start_file))
       
   843     goto flush_failed;
       
   844 
       
   845 #ifdef HAVE_FSEEKO
       
   846   if (fseeko (qtmux->fast_start_file, (off_t) 0, SEEK_SET) != 0)
       
   847     goto seek_failed;
       
   848 #elif defined (G_OS_UNIX) || defined (G_OS_WIN32)
       
   849   if (lseek (fileno (qtmux->fast_start_file), (off_t) 0,
       
   850           SEEK_SET) == (off_t) - 1)
       
   851     goto seek_failed;
       
   852 #else
       
   853   if (fseek (qtmux->fast_start_file, (long) 0, SEEK_SET) != 0)
       
   854     goto seek_failed;
       
   855 #endif
       
   856 
       
   857   /* hm, this could all take a really really long time,
       
   858    * but there may not be another way to get moov atom first
       
   859    * (somehow optimize copy?) */
       
   860   GST_DEBUG_OBJECT (qtmux, "Sending buffered data");
       
   861   while (ret == GST_FLOW_OK) {
       
   862     gint r;
       
   863     const int bufsize = 4096;
       
   864 
       
   865     buf = gst_buffer_new_and_alloc (bufsize);
       
   866     r = fread (GST_BUFFER_DATA (buf), sizeof (guint8), bufsize,
       
   867         qtmux->fast_start_file);
       
   868     if (r == 0)
       
   869       break;
       
   870     GST_BUFFER_SIZE (buf) = r;
       
   871     GST_LOG_OBJECT (qtmux, "Pushing buffered buffer of size %d", r);
       
   872     ret = gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
       
   873     buf = NULL;
       
   874   }
       
   875   if (buf)
       
   876     gst_buffer_unref (buf);
       
   877 
       
   878 exit:
       
   879   /* best cleaning up effort, eat possible error */
       
   880   fclose (qtmux->fast_start_file);
       
   881   qtmux->fast_start_file = NULL;
       
   882 
       
   883   /* FIXME maybe delete temporary file, or let the system handle that ? */
       
   884 
       
   885   return ret;
       
   886 
       
   887   /* ERRORS */
       
   888 flush_failed:
       
   889   {
       
   890     GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
       
   891         ("Failed to flush temporary file"), GST_ERROR_SYSTEM);
       
   892     ret = GST_FLOW_ERROR;
       
   893     goto exit;
       
   894   }
       
   895 seek_failed:
       
   896   {
       
   897     GST_ELEMENT_ERROR (qtmux, RESOURCE, SEEK,
       
   898         ("Failed to seek temporary file"), GST_ERROR_SYSTEM);
       
   899     ret = GST_FLOW_ERROR;
       
   900     goto exit;
       
   901   }
       
   902 }
       
   903 
       
   904 /*
       
   905  * Sends the initial mdat atom fields (size fields and fourcc type),
       
   906  * the subsequent buffers are considered part of it's data.
       
   907  * As we can't predict the amount of data that we are going to place in mdat
       
   908  * we need to record the position of the size field in the stream so we can
       
   909  * seek back to it later and update when the streams have finished.
       
   910  */
       
   911 static GstFlowReturn
       
   912 gst_qt_mux_send_mdat_header (GstQTMux * qtmux, guint64 * off, guint64 size,
       
   913     gboolean extended)
       
   914 {
       
   915   Atom *node_header;
       
   916   GstBuffer *buf;
       
   917   guint8 *data = NULL;
       
   918   guint64 offset = 0;
       
   919 
       
   920   GST_DEBUG_OBJECT (qtmux, "Sending mdat's atom header, "
       
   921       "size %" G_GUINT64_FORMAT, size);
       
   922 
       
   923   node_header = g_malloc0 (sizeof (Atom));
       
   924   node_header->type = FOURCC_mdat;
       
   925   if (extended) {
       
   926     /* use extended size */
       
   927     node_header->size = 1;
       
   928     node_header->extended_size = 0;
       
   929     if (size)
       
   930       node_header->extended_size = size + 16;
       
   931   } else {
       
   932     node_header->size = size + 8;
       
   933   }
       
   934 
       
   935   size = offset = 0;
       
   936   if (atom_copy_data (node_header, &data, &size, &offset) == 0)
       
   937     goto serialize_error;
       
   938 
       
   939   buf = gst_buffer_new ();
       
   940   GST_BUFFER_DATA (buf) = GST_BUFFER_MALLOCDATA (buf) = data;
       
   941   GST_BUFFER_SIZE (buf) = offset;
       
   942 
       
   943   g_free (node_header);
       
   944 
       
   945   GST_LOG_OBJECT (qtmux, "Pushing mdat start");
       
   946   return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
       
   947 
       
   948   /* ERRORS */
       
   949 serialize_error:
       
   950   {
       
   951     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
       
   952         ("Failed to serialize ftyp"));
       
   953     return GST_FLOW_ERROR;
       
   954   }
       
   955 }
       
   956 
       
   957 /*
       
   958  * We get the position of the mdat size field, seek back to it
       
   959  * and overwrite with the real value
       
   960  */
       
   961 static GstFlowReturn
       
   962 gst_qt_mux_update_mdat_size (GstQTMux * qtmux, guint64 mdat_pos,
       
   963     guint64 mdat_size, guint64 * offset)
       
   964 {
       
   965   GstEvent *event;
       
   966   GstBuffer *buf;
       
   967   gboolean large_file;
       
   968 
       
   969   large_file = (mdat_size > MDAT_LARGE_FILE_LIMIT);
       
   970 
       
   971   if (large_file)
       
   972     mdat_pos += 8;
       
   973 
       
   974   /* seek and rewrite the header */
       
   975   event = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES,
       
   976       mdat_pos, GST_CLOCK_TIME_NONE, 0);
       
   977   gst_pad_push_event (qtmux->srcpad, event);
       
   978 
       
   979   if (large_file) {
       
   980     buf = gst_buffer_new_and_alloc (sizeof (guint64));
       
   981     GST_WRITE_UINT64_BE (GST_BUFFER_DATA (buf), mdat_size + 16);
       
   982   } else {
       
   983     guint8 *data;
       
   984 
       
   985     buf = gst_buffer_new_and_alloc (16);
       
   986     data = GST_BUFFER_DATA (buf);
       
   987     GST_WRITE_UINT32_BE (data, 8);
       
   988     GST_WRITE_UINT32_LE (data + 4, FOURCC_free);
       
   989     GST_WRITE_UINT32_BE (data + 8, mdat_size + 8);
       
   990     GST_WRITE_UINT32_LE (data + 12, FOURCC_mdat);
       
   991   }
       
   992 
       
   993   return gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
       
   994 }
       
   995 
       
   996 static GstFlowReturn
       
   997 gst_qt_mux_send_ftyp (GstQTMux * qtmux, guint64 * off)
       
   998 {
       
   999   GstBuffer *buf;
       
  1000   guint64 size = 0, offset = 0;
       
  1001   guint8 *data = NULL;
       
  1002 
       
  1003   GST_DEBUG_OBJECT (qtmux, "Sending ftyp atom");
       
  1004 
       
  1005   if (!atom_ftyp_copy_data (qtmux->ftyp, &data, &size, &offset))
       
  1006     goto serialize_error;
       
  1007 
       
  1008   buf = gst_buffer_new ();
       
  1009   GST_BUFFER_DATA (buf) = GST_BUFFER_MALLOCDATA (buf) = data;
       
  1010   GST_BUFFER_SIZE (buf) = offset;
       
  1011 
       
  1012   GST_LOG_OBJECT (qtmux, "Pushing ftyp");
       
  1013   return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
       
  1014 
       
  1015   /* ERRORS */
       
  1016 serialize_error:
       
  1017   {
       
  1018     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
       
  1019         ("Failed to serialize ftyp"));
       
  1020     return GST_FLOW_ERROR;
       
  1021   }
       
  1022 }
       
  1023 
       
  1024 static GstFlowReturn
       
  1025 gst_qt_mux_prepare_and_send_ftyp (GstQTMux * qtmux)
       
  1026 {
       
  1027   GstFlowReturn ret = GST_FLOW_OK;
       
  1028   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
       
  1029   guint32 major, version;
       
  1030   GList *comp;
       
  1031   GstBuffer *prefix;
       
  1032 
       
  1033   GST_DEBUG_OBJECT (qtmux, "Preparing to send ftyp atom");
       
  1034 
       
  1035   /* init and send context and ftyp based on current property state */
       
  1036   if (qtmux->ftyp)
       
  1037     atom_ftyp_free (qtmux->ftyp);
       
  1038   gst_qt_mux_map_format_to_header (qtmux_klass->format, &prefix, &major,
       
  1039       &version, &comp, qtmux->moov, qtmux->longest_chunk,
       
  1040       qtmux->fast_start_file != NULL);
       
  1041   qtmux->ftyp = atom_ftyp_new (qtmux->context, major, version, comp);
       
  1042   if (comp)
       
  1043     g_list_free (comp);
       
  1044   if (prefix) {
       
  1045     ret = gst_qt_mux_send_buffer (qtmux, prefix, &qtmux->header_size, FALSE);
       
  1046     if (ret != GST_FLOW_OK)
       
  1047       return ret;
       
  1048   }
       
  1049   return gst_qt_mux_send_ftyp (qtmux, &qtmux->header_size);
       
  1050 }
       
  1051 
       
  1052 static GstFlowReturn
       
  1053 gst_qt_mux_start_file (GstQTMux * qtmux)
       
  1054 {
       
  1055   GstFlowReturn ret = GST_FLOW_OK;
       
  1056 
       
  1057   GST_DEBUG_OBJECT (qtmux, "starting file");
       
  1058 
       
  1059   /* let downstream know we think in BYTES and expect to do seeking later on */
       
  1060   gst_pad_push_event (qtmux->srcpad,
       
  1061       gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, 0, -1, 0));
       
  1062 
       
  1063   /* 
       
  1064    * send mdat header if already needed, and mark position for later update.
       
  1065    * We don't send ftyp now if we are on fast start mode, because we can
       
  1066    * better fine tune using the information we gather to create the whole moov
       
  1067    * atom.
       
  1068    */
       
  1069   if (qtmux->fast_start) {
       
  1070     GST_OBJECT_LOCK (qtmux);
       
  1071     qtmux->fast_start_file = g_fopen (qtmux->fast_start_file_path, "wb+");
       
  1072     if (!qtmux->fast_start_file)
       
  1073       goto open_failed;
       
  1074     GST_OBJECT_UNLOCK (qtmux);
       
  1075 
       
  1076     /* send a dummy buffer for preroll */
       
  1077     ret = gst_qt_mux_send_buffer (qtmux, gst_buffer_new (), NULL, FALSE);
       
  1078     if (ret != GST_FLOW_OK)
       
  1079       goto exit;
       
  1080 
       
  1081   } else {
       
  1082     ret = gst_qt_mux_prepare_and_send_ftyp (qtmux);
       
  1083     if (ret != GST_FLOW_OK) {
       
  1084       goto exit;
       
  1085     }
       
  1086 
       
  1087     /* extended to ensure some spare space */
       
  1088     qtmux->mdat_pos = qtmux->header_size;
       
  1089     ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0, TRUE);
       
  1090   }
       
  1091 
       
  1092 exit:
       
  1093   return ret;
       
  1094 
       
  1095   /* ERRORS */
       
  1096 open_failed:
       
  1097   {
       
  1098     GST_ELEMENT_ERROR (qtmux, RESOURCE, OPEN_READ_WRITE,
       
  1099         (("Could not open temporary file \"%s\""), qtmux->fast_start_file_path),
       
  1100         GST_ERROR_SYSTEM);
       
  1101     GST_OBJECT_UNLOCK (qtmux);
       
  1102     return GST_FLOW_ERROR;
       
  1103   }
       
  1104 }
       
  1105 
       
  1106 static GstFlowReturn
       
  1107 gst_qt_mux_stop_file (GstQTMux * qtmux)
       
  1108 {
       
  1109   gboolean ret = GST_FLOW_OK;
       
  1110   GstBuffer *buffer = NULL;
       
  1111   guint64 offset = 0, size = 0;
       
  1112   guint8 *data;
       
  1113   GSList *walk;
       
  1114   gboolean large_file;
       
  1115   guint32 timescale;
       
  1116 
       
  1117   GST_DEBUG_OBJECT (qtmux, "Updating remaining values and sending last data");
       
  1118 
       
  1119   /* pushing last buffers for each pad */
       
  1120   for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
       
  1121     GstCollectData *cdata = (GstCollectData *) walk->data;
       
  1122     GstQTPad *qtpad = (GstQTPad *) cdata;
       
  1123 
       
  1124     /* send last buffer */
       
  1125     GST_DEBUG_OBJECT (qtmux, "Sending the last buffer for pad %s",
       
  1126         GST_PAD_NAME (qtpad->collect.pad));
       
  1127     ret = gst_qt_mux_add_buffer (qtmux, qtpad, NULL);
       
  1128     if (ret != GST_FLOW_OK)
       
  1129       GST_WARNING_OBJECT (qtmux, "Failed to send last buffer for %s, "
       
  1130           "flow return: %s", GST_PAD_NAME (qtpad->collect.pad),
       
  1131           gst_flow_get_name (ret));
       
  1132   }
       
  1133 
       
  1134   GST_OBJECT_LOCK (qtmux);
       
  1135   timescale = qtmux->timescale;
       
  1136   large_file = qtmux->large_file;
       
  1137   GST_OBJECT_UNLOCK (qtmux);
       
  1138 
       
  1139   /* inform lower layers of our property wishes, and determine duration.
       
  1140    * Let moov take care of this using its list of traks;
       
  1141    * so that released pads are also included */
       
  1142   GST_DEBUG_OBJECT (qtmux, "Large file support: %d", large_file);
       
  1143   GST_DEBUG_OBJECT (qtmux, "Updating timescale to %" G_GUINT32_FORMAT,
       
  1144       timescale);
       
  1145   atom_moov_update_timescale (qtmux->moov, timescale);
       
  1146   atom_moov_set_64bits (qtmux->moov, large_file);
       
  1147   atom_moov_update_duration (qtmux->moov);
       
  1148 
       
  1149   /* tags into file metadata */
       
  1150   gst_qt_mux_setup_metadata (qtmux);
       
  1151 
       
  1152   large_file = (qtmux->mdat_size > MDAT_LARGE_FILE_LIMIT);
       
  1153   /* if faststart, update the offset of the atoms in the movie with the offset
       
  1154    * that the movie headers before mdat will cause.
       
  1155    * Also, send the ftyp */
       
  1156   if (qtmux->fast_start_file) {
       
  1157     GstFlowReturn flow_ret;
       
  1158     offset = size = 0;
       
  1159 
       
  1160     flow_ret = gst_qt_mux_prepare_and_send_ftyp (qtmux);
       
  1161     if (flow_ret != GST_FLOW_OK) {
       
  1162       goto ftyp_error;
       
  1163     }
       
  1164     /* copy into NULL to obtain size */
       
  1165     if (!atom_moov_copy_data (qtmux->moov, NULL, &size, &offset))
       
  1166       goto serialize_error;
       
  1167     GST_DEBUG_OBJECT (qtmux, "calculated moov atom size %" G_GUINT64_FORMAT,
       
  1168         size);
       
  1169     offset += qtmux->header_size + (large_file ? 16 : 8);
       
  1170   } else
       
  1171     offset = qtmux->header_size;
       
  1172   atom_moov_chunks_add_offset (qtmux->moov, offset);
       
  1173 
       
  1174   /* serialize moov */
       
  1175   offset = size = 0;
       
  1176   data = NULL;
       
  1177   GST_LOG_OBJECT (qtmux, "Copying movie header into buffer");
       
  1178   ret = atom_moov_copy_data (qtmux->moov, &data, &size, &offset);
       
  1179   if (!ret)
       
  1180     goto serialize_error;
       
  1181 
       
  1182   buffer = gst_buffer_new ();
       
  1183   GST_BUFFER_DATA (buffer) = GST_BUFFER_MALLOCDATA (buffer) = data;
       
  1184   GST_BUFFER_SIZE (buffer) = offset;
       
  1185   /* note: as of this point, we no longer care about tracking written data size,
       
  1186    * since there is no more use for it anyway */
       
  1187   GST_DEBUG_OBJECT (qtmux, "Pushing movie atoms");
       
  1188   gst_qt_mux_send_buffer (qtmux, buffer, NULL, FALSE);
       
  1189 
       
  1190   /* if needed, send mdat atom and move buffered data into it */
       
  1191   if (qtmux->fast_start_file) {
       
  1192     /* mdat size = accumulated (buffered data) + mdat atom header */
       
  1193     ret = gst_qt_mux_send_mdat_header (qtmux, NULL, qtmux->mdat_size,
       
  1194         large_file);
       
  1195     if (ret != GST_FLOW_OK)
       
  1196       return ret;
       
  1197     ret = gst_qt_mux_send_buffered_data (qtmux, NULL);
       
  1198     if (ret != GST_FLOW_OK)
       
  1199       return ret;
       
  1200   } else {
       
  1201     /* mdata needs update iff not using faststart */
       
  1202     GST_DEBUG_OBJECT (qtmux, "updating mdata size");
       
  1203     ret = gst_qt_mux_update_mdat_size (qtmux, qtmux->mdat_pos,
       
  1204         qtmux->mdat_size, NULL);
       
  1205     /* note; no seeking back to the end of file is done,
       
  1206      * since we longer write anything anyway */
       
  1207   }
       
  1208 
       
  1209   return ret;
       
  1210 
       
  1211   /* ERRORS */
       
  1212 serialize_error:
       
  1213   {
       
  1214     gst_buffer_unref (buffer);
       
  1215     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
       
  1216         ("Failed to serialize moov"));
       
  1217     return GST_FLOW_ERROR;
       
  1218   }
       
  1219 ftyp_error:
       
  1220   {
       
  1221     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL), ("Failed to send ftyp"));
       
  1222     return GST_FLOW_ERROR;
       
  1223   }
       
  1224 }
       
  1225 
       
  1226 /*
       
  1227  * Here we push the buffer and update the tables in the track atoms
       
  1228  */
       
  1229 static GstFlowReturn
       
  1230 gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf)
       
  1231 {
       
  1232   GstBuffer *last_buf = NULL;
       
  1233   GstClockTime duration;
       
  1234   guint nsamples, sample_size;
       
  1235   guint64 scaled_duration, chunk_offset;
       
  1236   gint64 last_dts;
       
  1237   gint64 pts_offset = 0;
       
  1238   gboolean sync = FALSE, do_pts = FALSE;
       
  1239 
       
  1240   if (!pad->fourcc)
       
  1241     goto not_negotiated;
       
  1242 
       
  1243   last_buf = pad->last_buf;
       
  1244   if (last_buf == NULL) {
       
  1245 #ifndef GST_DISABLE_GST_DEBUG
       
  1246     if (buf == NULL) {
       
  1247       GST_DEBUG_OBJECT (qtmux, "Pad %s has no previous buffer stored and "
       
  1248           "received NULL buffer, doing nothing",
       
  1249           GST_PAD_NAME (pad->collect.pad));
       
  1250     } else {
       
  1251       GST_LOG_OBJECT (qtmux,
       
  1252           "Pad %s has no previous buffer stored, storing now",
       
  1253           GST_PAD_NAME (pad->collect.pad));
       
  1254     }
       
  1255 #endif
       
  1256     pad->last_buf = buf;
       
  1257     return GST_FLOW_OK;
       
  1258   } else
       
  1259     gst_buffer_ref (last_buf);
       
  1260 
       
  1261   /* fall back to duration if:
       
  1262    * - last bufer
       
  1263    * - this format has out of order buffers (e.g. MPEG-4),
       
  1264    * - lack of valid time forces fall back */
       
  1265   if (buf == NULL || pad->is_out_of_order ||
       
  1266       !GST_BUFFER_TIMESTAMP_IS_VALID (last_buf) ||
       
  1267       !GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
       
  1268     if (!GST_BUFFER_DURATION_IS_VALID (last_buf)) {
       
  1269       /* be forgiving for some possibly last upstream flushed buffer */
       
  1270       if (buf)
       
  1271         goto no_time;
       
  1272       GST_WARNING_OBJECT (qtmux, "no duration for last buffer");
       
  1273       /* iso spec recommends some small value, try 0 */
       
  1274       duration = 0;
       
  1275     } else {
       
  1276       duration = GST_BUFFER_DURATION (last_buf);
       
  1277     }
       
  1278   } else {
       
  1279     duration = GST_BUFFER_TIMESTAMP (buf) - GST_BUFFER_TIMESTAMP (last_buf);
       
  1280   }
       
  1281 
       
  1282   gst_buffer_replace (&pad->last_buf, buf);
       
  1283 
       
  1284   last_dts = gst_util_uint64_scale (pad->last_dts,
       
  1285       atom_trak_get_timescale (pad->trak), GST_SECOND);
       
  1286 
       
  1287   /* raw audio has many samples per buffer (= chunk) */
       
  1288   if (pad->sample_size) {
       
  1289     sample_size = pad->sample_size;
       
  1290     if (GST_BUFFER_SIZE (last_buf) % sample_size != 0)
       
  1291       goto fragmented_sample;
       
  1292     /* note: qt raw audio storage warps it implicitly into a timewise
       
  1293      * perfect stream, discarding buffer times */
       
  1294     nsamples = GST_BUFFER_SIZE (last_buf) / sample_size;
       
  1295     duration = GST_BUFFER_DURATION (last_buf) / nsamples;
       
  1296     /* timescale = samplerate */
       
  1297     scaled_duration = 1;
       
  1298     pad->last_dts += duration * nsamples;
       
  1299   } else {
       
  1300     nsamples = 1;
       
  1301     sample_size = GST_BUFFER_SIZE (last_buf);
       
  1302     if (pad->have_dts) {
       
  1303       gint64 scaled_dts;
       
  1304       pad->last_dts = GST_BUFFER_OFFSET_END (last_buf);
       
  1305       if ((gint64) (pad->last_dts) < 0) {
       
  1306         scaled_dts = -gst_util_uint64_scale (-pad->last_dts,
       
  1307             atom_trak_get_timescale (pad->trak), GST_SECOND);
       
  1308       } else {
       
  1309         scaled_dts = gst_util_uint64_scale (pad->last_dts,
       
  1310             atom_trak_get_timescale (pad->trak), GST_SECOND);
       
  1311       }
       
  1312       scaled_duration = scaled_dts - last_dts;
       
  1313       last_dts = scaled_dts;
       
  1314     } else {
       
  1315       /* first convert intended timestamp (in GstClockTime resolution) to
       
  1316        * trak timescale, then derive delta;
       
  1317        * this ensures sums of (scale)delta add up to converted timestamp,
       
  1318        * which only deviates at most 1/scale from timestamp itself */
       
  1319       scaled_duration = gst_util_uint64_scale (pad->last_dts + duration,
       
  1320           atom_trak_get_timescale (pad->trak), GST_SECOND) - last_dts;
       
  1321       pad->last_dts += duration;
       
  1322     }
       
  1323   }
       
  1324   chunk_offset = qtmux->mdat_size;
       
  1325 
       
  1326   GST_LOG_OBJECT (qtmux,
       
  1327       "Pad (%s) dts updated to %" GST_TIME_FORMAT,
       
  1328       GST_PAD_NAME (pad->collect.pad), GST_TIME_ARGS (pad->last_dts));
       
  1329   GST_LOG_OBJECT (qtmux,
       
  1330       "Adding %d samples to track, duration: %" G_GUINT64_FORMAT
       
  1331       " size: %" G_GUINT32_FORMAT " chunk offset: %" G_GUINT64_FORMAT,
       
  1332       nsamples, scaled_duration, sample_size, chunk_offset);
       
  1333 
       
  1334   /* might be a sync sample */
       
  1335   if (pad->sync &&
       
  1336       !GST_BUFFER_FLAG_IS_SET (last_buf, GST_BUFFER_FLAG_DELTA_UNIT)) {
       
  1337     GST_LOG_OBJECT (qtmux, "Adding new sync sample entry for track of pad %s",
       
  1338         GST_PAD_NAME (pad->collect.pad));
       
  1339     sync = TRUE;
       
  1340   }
       
  1341 
       
  1342   /* optionally calculate ctts entry values
       
  1343    * (if composition-time expected different from decoding-time) */
       
  1344   /* really not recommended:
       
  1345    * - decoder typically takes care of dts/pts issues
       
  1346    * - in case of out-of-order, dts may only be determined as above
       
  1347    *   (e.g. sum of duration), which may be totally different from
       
  1348    *   buffer timestamps in case of multiple segment, non-perfect streams
       
  1349    *  (and just perhaps maybe with some luck segment_to_running_time
       
  1350    *   or segment_to_media_time might get near to it) */
       
  1351   if ((pad->have_dts || qtmux->guess_pts) && pad->is_out_of_order) {
       
  1352     guint64 pts;
       
  1353 
       
  1354     pts = gst_util_uint64_scale (GST_BUFFER_TIMESTAMP (last_buf),
       
  1355         atom_trak_get_timescale (pad->trak), GST_SECOND);
       
  1356     pts_offset = (gint64) (pts - last_dts);
       
  1357     do_pts = TRUE;
       
  1358     GST_LOG_OBJECT (qtmux, "Adding ctts entry for pad %s: %" G_GINT64_FORMAT,
       
  1359         GST_PAD_NAME (pad->collect.pad), pts_offset);
       
  1360   }
       
  1361 
       
  1362   /*
       
  1363    * Each buffer starts a new chunk, so we can assume the buffer
       
  1364    * duration is the chunk duration
       
  1365    */
       
  1366   if (GST_CLOCK_TIME_IS_VALID (duration) && (duration > qtmux->longest_chunk ||
       
  1367           !GST_CLOCK_TIME_IS_VALID (qtmux->longest_chunk))) {
       
  1368     GST_DEBUG_OBJECT (qtmux, "New longest chunk found: %" GST_TIME_FORMAT
       
  1369         ", pad %s", GST_TIME_ARGS (duration), GST_PAD_NAME (pad->collect.pad));
       
  1370     qtmux->longest_chunk = duration;
       
  1371   }
       
  1372 
       
  1373   /* now we go and register this buffer/sample all over */
       
  1374   /* note that a new chunk is started each time (not fancy but works) */
       
  1375   atom_trak_add_samples (pad->trak, nsamples, scaled_duration, sample_size,
       
  1376       chunk_offset, sync, do_pts, pts_offset);
       
  1377 
       
  1378   if (buf)
       
  1379     gst_buffer_unref (buf);
       
  1380 
       
  1381   return gst_qt_mux_send_buffer (qtmux, last_buf, &qtmux->mdat_size, TRUE);
       
  1382 
       
  1383   /* ERRORS */
       
  1384 bail:
       
  1385   {
       
  1386     if (buf)
       
  1387       gst_buffer_unref (buf);
       
  1388     gst_buffer_unref (last_buf);
       
  1389     return GST_FLOW_ERROR;
       
  1390   }
       
  1391 no_time:
       
  1392   {
       
  1393     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
       
  1394         ("Received buffer without timestamp/duration."));
       
  1395     goto bail;
       
  1396   }
       
  1397 fragmented_sample:
       
  1398   {
       
  1399     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
       
  1400         ("Audio buffer contains fragmented sample."));
       
  1401     goto bail;
       
  1402   }
       
  1403 not_negotiated:
       
  1404   {
       
  1405     GST_ELEMENT_ERROR (qtmux, CORE, NEGOTIATION, (NULL),
       
  1406         ("format wasn't negotiated before buffer flow on pad %s",
       
  1407             GST_PAD_NAME (pad->collect.pad)));
       
  1408     if (buf)
       
  1409       gst_buffer_unref (buf);
       
  1410     return GST_FLOW_NOT_NEGOTIATED;
       
  1411   }
       
  1412 }
       
  1413 
       
  1414 static GstFlowReturn
       
  1415 gst_qt_mux_collected (GstCollectPads * pads, gpointer user_data)
       
  1416 {
       
  1417   GstFlowReturn ret = GST_FLOW_OK;
       
  1418   GstQTMux *qtmux = GST_QT_MUX_CAST (user_data);
       
  1419   GSList *walk;
       
  1420   GstQTPad *best_pad = NULL;
       
  1421   GstClockTime time, best_time = GST_CLOCK_TIME_NONE;
       
  1422   GstBuffer *buf;
       
  1423 
       
  1424   if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_STARTED)) {
       
  1425     if ((ret = gst_qt_mux_start_file (qtmux)) != GST_FLOW_OK)
       
  1426       return ret;
       
  1427     else
       
  1428       qtmux->state = GST_QT_MUX_STATE_DATA;
       
  1429   }
       
  1430 
       
  1431   if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_EOS))
       
  1432     return GST_FLOW_UNEXPECTED;
       
  1433 
       
  1434   /* select the best buffer */
       
  1435   walk = qtmux->collect->data;
       
  1436   while (walk) {
       
  1437     GstQTPad *pad;
       
  1438     GstCollectData *data;
       
  1439 
       
  1440     data = (GstCollectData *) walk->data;
       
  1441     pad = (GstQTPad *) data;
       
  1442 
       
  1443     walk = g_slist_next (walk);
       
  1444 
       
  1445     buf = gst_collect_pads_peek (pads, data);
       
  1446     if (buf == NULL) {
       
  1447       GST_LOG_OBJECT (qtmux, "Pad %s has no buffers",
       
  1448           GST_PAD_NAME (pad->collect.pad));
       
  1449       continue;
       
  1450     }
       
  1451     time = GST_BUFFER_TIMESTAMP (buf);
       
  1452     gst_buffer_unref (buf);
       
  1453 
       
  1454     if (best_pad == NULL || !GST_CLOCK_TIME_IS_VALID (time) ||
       
  1455         (GST_CLOCK_TIME_IS_VALID (best_time) && time < best_time)) {
       
  1456       best_pad = pad;
       
  1457       best_time = time;
       
  1458     }
       
  1459   }
       
  1460 
       
  1461   if (best_pad != NULL) {
       
  1462     GST_LOG_OBJECT (qtmux, "selected pad %s with time %" GST_TIME_FORMAT,
       
  1463         GST_PAD_NAME (best_pad->collect.pad), GST_TIME_ARGS (best_time));
       
  1464     buf = gst_collect_pads_pop (pads, &best_pad->collect);
       
  1465     ret = gst_qt_mux_add_buffer (qtmux, best_pad, buf);
       
  1466   } else {
       
  1467     ret = gst_qt_mux_stop_file (qtmux);
       
  1468     if (ret == GST_FLOW_OK) {
       
  1469       gst_pad_push_event (qtmux->srcpad, gst_event_new_eos ());
       
  1470       ret = GST_FLOW_UNEXPECTED;
       
  1471     }
       
  1472     qtmux->state = GST_QT_MUX_STATE_EOS;
       
  1473   }
       
  1474 
       
  1475   return ret;
       
  1476 }
       
  1477 
       
  1478 static gboolean
       
  1479 gst_qt_mux_audio_sink_set_caps (GstPad * pad, GstCaps * caps)
       
  1480 {
       
  1481   GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
       
  1482   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
       
  1483   GstQTPad *qtpad = NULL;
       
  1484   GstStructure *structure;
       
  1485   const gchar *mimetype;
       
  1486   gint rate, channels;
       
  1487   const GValue *value = NULL;
       
  1488   const GstBuffer *codec_data = NULL;
       
  1489   GstQTMuxFormat format;
       
  1490   AudioSampleEntry entry = { 0, };
       
  1491   AtomInfo *ext_atom = NULL;
       
  1492   gint constant_size = 0;
       
  1493 
       
  1494   /* find stream data */
       
  1495   qtpad = (GstQTPad *) gst_pad_get_element_private (pad);
       
  1496   g_assert (qtpad);
       
  1497 
       
  1498   /* does not go well to renegotiate stream mid-way */
       
  1499   if (qtpad->fourcc)
       
  1500     goto refuse_renegotiation;
       
  1501 
       
  1502   GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
       
  1503       GST_DEBUG_PAD_NAME (pad), caps);
       
  1504 
       
  1505   format = qtmux_klass->format;
       
  1506   structure = gst_caps_get_structure (caps, 0);
       
  1507   mimetype = gst_structure_get_name (structure);
       
  1508 
       
  1509   /* common info */
       
  1510   if (!gst_structure_get_int (structure, "channels", &channels) ||
       
  1511       !gst_structure_get_int (structure, "rate", &rate)) {
       
  1512     goto refuse_caps;
       
  1513   }
       
  1514 
       
  1515   /* optional */
       
  1516   value = gst_structure_get_value (structure, "codec_data");
       
  1517   if (value != NULL)
       
  1518     codec_data = gst_value_get_buffer (value);
       
  1519 
       
  1520   qtpad->is_out_of_order = FALSE;
       
  1521   qtpad->have_dts = FALSE;
       
  1522 
       
  1523   /* set common properties */
       
  1524   entry.sample_rate = rate;
       
  1525   entry.channels = channels;
       
  1526   /* default */
       
  1527   entry.sample_size = 16;
       
  1528   /* this is the typical compressed case */
       
  1529   if (format == GST_QT_MUX_FORMAT_QT) {
       
  1530     entry.version = 1;
       
  1531     entry.compression_id = -2;
       
  1532   }
       
  1533 
       
  1534   /* now map onto a fourcc, and some extra properties */
       
  1535   if (strcmp (mimetype, "audio/mpeg") == 0) {
       
  1536     gint mpegversion = 0;
       
  1537     gint layer = -1;
       
  1538 
       
  1539     gst_structure_get_int (structure, "mpegversion", &mpegversion);
       
  1540     switch (mpegversion) {
       
  1541       case 1:
       
  1542         gst_structure_get_int (structure, "layer", &layer);
       
  1543         switch (layer) {
       
  1544           case 3:
       
  1545             /* mp3 */
       
  1546             /* note: QuickTime player does not like mp3 either way in iso/mp4 */
       
  1547             if (format == GST_QT_MUX_FORMAT_QT)
       
  1548               entry.fourcc = FOURCC__mp3;
       
  1549             else {
       
  1550               entry.fourcc = FOURCC_mp4a;
       
  1551               ext_atom =
       
  1552                   build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG1_P3,
       
  1553                   ESDS_STREAM_TYPE_AUDIO, codec_data);
       
  1554             }
       
  1555             entry.samples_per_packet = 1152;
       
  1556             entry.bytes_per_sample = 2;
       
  1557             break;
       
  1558         }
       
  1559         break;
       
  1560       case 4:
       
  1561         /* AAC */
       
  1562         entry.fourcc = FOURCC_mp4a;
       
  1563         if (!codec_data || GST_BUFFER_SIZE (codec_data) < 2)
       
  1564           GST_WARNING_OBJECT (qtmux, "no (valid) codec_data for AAC audio");
       
  1565         else {
       
  1566           guint8 profile = GST_READ_UINT8 (GST_BUFFER_DATA (codec_data));
       
  1567 
       
  1568           /* warn if not Low Complexity profile */
       
  1569           profile >>= 3;
       
  1570           if (profile != 2)
       
  1571             GST_WARNING_OBJECT (qtmux,
       
  1572                 "non-LC AAC may not run well on (Apple) QuickTime/iTunes");
       
  1573         }
       
  1574         if (format == GST_QT_MUX_FORMAT_QT)
       
  1575           ext_atom = build_mov_aac_extension (qtpad->trak, codec_data);
       
  1576         else
       
  1577           ext_atom =
       
  1578               build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P3,
       
  1579               ESDS_STREAM_TYPE_AUDIO, codec_data);
       
  1580         break;
       
  1581       default:
       
  1582         break;
       
  1583     }
       
  1584   } else if (strcmp (mimetype, "audio/AMR") == 0) {
       
  1585     entry.fourcc = FOURCC_samr;
       
  1586     entry.sample_size = 16;
       
  1587     entry.samples_per_packet = 160;
       
  1588     entry.bytes_per_sample = 2;
       
  1589     ext_atom = build_amr_extension ();
       
  1590   } else if (strcmp (mimetype, "audio/AMR-WB") == 0) {
       
  1591     entry.fourcc = FOURCC_sawb;
       
  1592     entry.sample_size = 16;
       
  1593     entry.samples_per_packet = 320;
       
  1594     entry.bytes_per_sample = 2;
       
  1595     ext_atom = build_amr_extension ();
       
  1596   } else if (strcmp (mimetype, "audio/x-raw-int") == 0) {
       
  1597     gint width;
       
  1598     gint depth;
       
  1599     gint endianness;
       
  1600     gboolean sign;
       
  1601 
       
  1602     if (!gst_structure_get_int (structure, "width", &width) ||
       
  1603         !gst_structure_get_int (structure, "depth", &depth) ||
       
  1604         !gst_structure_get_boolean (structure, "signed", &sign)) {
       
  1605       GST_DEBUG_OBJECT (qtmux, "broken caps, width/depth/signed field missing");
       
  1606       goto refuse_caps;
       
  1607     }
       
  1608 
       
  1609     if (depth <= 8) {
       
  1610       endianness = G_BYTE_ORDER;
       
  1611     } else if (!gst_structure_get_boolean (structure,
       
  1612             "endianness", &endianness)) {
       
  1613       GST_DEBUG_OBJECT (qtmux, "broken caps, endianness field missing");
       
  1614       goto refuse_caps;
       
  1615     }
       
  1616 
       
  1617     /* spec has no place for a distinction in these */
       
  1618     if (width != depth) {
       
  1619       GST_DEBUG_OBJECT (qtmux, "width must be same as depth!");
       
  1620       goto refuse_caps;
       
  1621     }
       
  1622 
       
  1623     if (sign) {
       
  1624       if (endianness == G_LITTLE_ENDIAN)
       
  1625         entry.fourcc = FOURCC_sowt;
       
  1626       else if (endianness == G_BIG_ENDIAN)
       
  1627         entry.fourcc = FOURCC_twos;
       
  1628       /* maximum backward compatibility; only new version for > 16 bit */
       
  1629       if (depth <= 16)
       
  1630         entry.version = 0;
       
  1631       /* not compressed in any case */
       
  1632       entry.compression_id = 0;
       
  1633       /* QT spec says: max at 16 bit even if sample size were actually larger,
       
  1634        * however, most players (e.g. QuickTime!) seem to disagree, so ... */
       
  1635       entry.sample_size = depth;
       
  1636       entry.bytes_per_sample = depth / 8;
       
  1637       entry.samples_per_packet = 1;
       
  1638       entry.bytes_per_packet = depth / 8;
       
  1639       entry.bytes_per_frame = entry.bytes_per_packet * channels;
       
  1640     } else {
       
  1641       if (width == 8 && depth == 8) {
       
  1642         /* fall back to old 8-bit version */
       
  1643         entry.fourcc = FOURCC_raw_;
       
  1644         entry.version = 0;
       
  1645         entry.compression_id = 0;
       
  1646         entry.sample_size = 8;
       
  1647       } else {
       
  1648         GST_DEBUG_OBJECT (qtmux, "non 8-bit PCM must be signed");
       
  1649         goto refuse_caps;
       
  1650       }
       
  1651     }
       
  1652     constant_size = (depth / 8) * channels;
       
  1653   } else if (strcmp (mimetype, "audio/x-alaw") == 0) {
       
  1654     entry.fourcc = FOURCC_alaw;
       
  1655     entry.samples_per_packet = 1023;
       
  1656     entry.bytes_per_sample = 2;
       
  1657   } else if (strcmp (mimetype, "audio/x-mulaw") == 0) {
       
  1658     entry.fourcc = FOURCC_ulaw;
       
  1659     entry.samples_per_packet = 1023;
       
  1660     entry.bytes_per_sample = 2;
       
  1661   }
       
  1662 
       
  1663   if (!entry.fourcc)
       
  1664     goto refuse_caps;
       
  1665 
       
  1666   /* ok, set the pad info accordingly */
       
  1667   qtpad->fourcc = entry.fourcc;
       
  1668   qtpad->sample_size = constant_size;
       
  1669   atom_trak_set_audio_type (qtpad->trak, qtmux->context, &entry,
       
  1670       entry.sample_rate, ext_atom, constant_size);
       
  1671 
       
  1672   gst_object_unref (qtmux);
       
  1673   return TRUE;
       
  1674 
       
  1675   /* ERRORS */
       
  1676 refuse_caps:
       
  1677   {
       
  1678     GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
       
  1679         GST_PAD_NAME (pad), caps);
       
  1680     gst_object_unref (qtmux);
       
  1681     return FALSE;
       
  1682   }
       
  1683 refuse_renegotiation:
       
  1684   {
       
  1685     GST_WARNING_OBJECT (qtmux,
       
  1686         "pad %s refused renegotiation to %" GST_PTR_FORMAT,
       
  1687         GST_PAD_NAME (pad), caps);
       
  1688     gst_object_unref (qtmux);
       
  1689     return FALSE;
       
  1690   }
       
  1691 }
       
  1692 
       
  1693 /* scale rate up or down by factor of 10 to fit into [1000,10000] interval */
       
  1694 static guint32
       
  1695 adjust_rate (guint64 rate)
       
  1696 {
       
  1697   while (rate >= 10000)
       
  1698     rate /= 10;
       
  1699 
       
  1700   while (rate < 1000)
       
  1701     rate *= 10;
       
  1702 
       
  1703   return (guint32) rate;
       
  1704 }
       
  1705 
       
  1706 static gboolean
       
  1707 gst_qt_mux_video_sink_set_caps (GstPad * pad, GstCaps * caps)
       
  1708 {
       
  1709   GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
       
  1710   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
       
  1711   GstQTPad *qtpad = NULL;
       
  1712   GstStructure *structure;
       
  1713   const gchar *mimetype;
       
  1714   gint width, height, depth = -1;
       
  1715   gint framerate_num, framerate_den;
       
  1716   guint32 rate;
       
  1717   const GValue *value = NULL;
       
  1718   const GstBuffer *codec_data = NULL;
       
  1719   VisualSampleEntry entry = { 0, };
       
  1720   GstQTMuxFormat format;
       
  1721   AtomInfo *ext_atom = NULL;
       
  1722   gboolean sync = FALSE;
       
  1723   int par_num, par_den;
       
  1724 
       
  1725   /* find stream data */
       
  1726   qtpad = (GstQTPad *) gst_pad_get_element_private (pad);
       
  1727   g_assert (qtpad);
       
  1728 
       
  1729   /* does not go well to renegotiate stream mid-way */
       
  1730   if (qtpad->fourcc)
       
  1731     goto refuse_renegotiation;
       
  1732 
       
  1733   GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
       
  1734       GST_DEBUG_PAD_NAME (pad), caps);
       
  1735 
       
  1736   format = qtmux_klass->format;
       
  1737   structure = gst_caps_get_structure (caps, 0);
       
  1738   mimetype = gst_structure_get_name (structure);
       
  1739 
       
  1740   /* required parts */
       
  1741   if (!gst_structure_get_int (structure, "width", &width) ||
       
  1742       !gst_structure_get_int (structure, "height", &height))
       
  1743     goto refuse_caps;
       
  1744 
       
  1745   /* optional */
       
  1746   depth = -1;
       
  1747   /* works as a default timebase */
       
  1748   framerate_num = 10000;
       
  1749   framerate_den = 1;
       
  1750   gst_structure_get_fraction (structure, "framerate", &framerate_num,
       
  1751       &framerate_den);
       
  1752   gst_structure_get_int (structure, "depth", &depth);
       
  1753   value = gst_structure_get_value (structure, "codec_data");
       
  1754   if (value != NULL)
       
  1755     codec_data = gst_value_get_buffer (value);
       
  1756 
       
  1757   par_num = 1;
       
  1758   par_den = 1;
       
  1759   gst_structure_get_fraction (structure, "pixel-aspect-ratio", &par_num,
       
  1760       &par_den);
       
  1761 
       
  1762   qtpad->is_out_of_order = FALSE;
       
  1763 
       
  1764   /* bring frame numerator into a range that ensures both reasonable resolution
       
  1765    * as well as a fair duration */
       
  1766   rate = adjust_rate (framerate_num);
       
  1767   GST_DEBUG_OBJECT (qtmux, "Rate of video track selected: %" G_GUINT32_FORMAT,
       
  1768       rate);
       
  1769 
       
  1770   /* set common properties */
       
  1771   entry.width = width;
       
  1772   entry.height = height;
       
  1773   entry.par_n = par_num;
       
  1774   entry.par_d = par_den;
       
  1775   /* should be OK according to qt and iso spec, override if really needed */
       
  1776   entry.color_table_id = -1;
       
  1777   entry.frame_count = 1;
       
  1778   entry.depth = 24;
       
  1779 
       
  1780   /* sync entries by default */
       
  1781   sync = TRUE;
       
  1782 
       
  1783   /* now map onto a fourcc, and some extra properties */
       
  1784   if (strcmp (mimetype, "video/x-raw-rgb") == 0) {
       
  1785     gint bpp;
       
  1786 
       
  1787     entry.fourcc = FOURCC_raw_;
       
  1788     gst_structure_get_int (structure, "bpp", &bpp);
       
  1789     entry.depth = bpp;
       
  1790     sync = FALSE;
       
  1791   } else if (strcmp (mimetype, "video/x-raw-yuv") == 0) {
       
  1792     guint32 format = 0;
       
  1793 
       
  1794     sync = FALSE;
       
  1795     gst_structure_get_fourcc (structure, "format", &format);
       
  1796     switch (format) {
       
  1797       case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'):
       
  1798         if (depth == -1)
       
  1799           depth = 24;
       
  1800         entry.fourcc = FOURCC_2vuy;
       
  1801         entry.depth = depth;
       
  1802         break;
       
  1803     }
       
  1804   } else if (strcmp (mimetype, "video/x-h263") == 0) {
       
  1805     if (format == GST_QT_MUX_FORMAT_QT)
       
  1806       entry.fourcc = FOURCC_h263;
       
  1807     else
       
  1808       entry.fourcc = FOURCC_s263;
       
  1809     ext_atom = build_h263_extension ();
       
  1810   } else if (strcmp (mimetype, "video/x-divx") == 0 ||
       
  1811       strcmp (mimetype, "video/mpeg") == 0) {
       
  1812     gint version = 0;
       
  1813 
       
  1814     if (strcmp (mimetype, "video/x-divx") == 0) {
       
  1815       gst_structure_get_int (structure, "divxversion", &version);
       
  1816       version = version == 5 ? 1 : 0;
       
  1817     } else {
       
  1818       gst_structure_get_int (structure, "mpegversion", &version);
       
  1819       version = version == 4 ? 1 : 0;
       
  1820     }
       
  1821     if (version) {
       
  1822       entry.fourcc = FOURCC_mp4v;
       
  1823       ext_atom =
       
  1824           build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P2,
       
  1825           ESDS_STREAM_TYPE_VISUAL, codec_data);
       
  1826       if (!codec_data)
       
  1827         GST_WARNING_OBJECT (qtmux, "no codec_data for MPEG4 video; "
       
  1828             "output might not play in Apple QuickTime (try global-headers?)");
       
  1829     }
       
  1830   } else if (strcmp (mimetype, "video/x-h264") == 0) {
       
  1831     entry.fourcc = FOURCC_avc1;
       
  1832     qtpad->is_out_of_order = TRUE;
       
  1833     if (!codec_data)
       
  1834       GST_WARNING_OBJECT (qtmux, "no codec_data in h264 caps");
       
  1835     ext_atom = build_codec_data_extension (FOURCC_avcC, codec_data);
       
  1836   } else if (strcmp (mimetype, "video/x-dv") == 0) {
       
  1837     gint version = 0;
       
  1838     gboolean pal = TRUE;
       
  1839 
       
  1840     sync = FALSE;
       
  1841     if (framerate_num != 25 || framerate_den != 1)
       
  1842       pal = FALSE;
       
  1843     gst_structure_get_int (structure, "dvversion", &version);
       
  1844     /* fall back to typical one */
       
  1845     if (!version)
       
  1846       version = 25;
       
  1847     switch (version) {
       
  1848       case 25:
       
  1849         if (pal)
       
  1850           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', 'p');
       
  1851         else
       
  1852           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', ' ');
       
  1853         break;
       
  1854       case 50:
       
  1855         if (pal)
       
  1856           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'p');
       
  1857         else
       
  1858           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'n');
       
  1859         break;
       
  1860       default:
       
  1861         GST_WARNING_OBJECT (qtmux, "unrecognized dv version");
       
  1862         break;
       
  1863     }
       
  1864   } else if (strcmp (mimetype, "image/jpeg") == 0) {
       
  1865     entry.fourcc = FOURCC_jpeg;
       
  1866     sync = FALSE;
       
  1867   } else if (strcmp (mimetype, "image/x-j2c") == 0) {
       
  1868     guint32 fourcc;
       
  1869 
       
  1870     entry.fourcc = FOURCC_mjp2;
       
  1871     sync = FALSE;
       
  1872     if (!gst_structure_get_fourcc (structure, "fourcc", &fourcc) ||
       
  1873         !(ext_atom =
       
  1874             build_jp2h_extension (qtpad->trak, width, height, fourcc))) {
       
  1875       GST_DEBUG_OBJECT (qtmux, "missing or invalid fourcc in jp2 caps");
       
  1876       goto refuse_caps;
       
  1877     }
       
  1878   } else if (strcmp (mimetype, "video/x-qt-part") == 0) {
       
  1879     guint32 fourcc;
       
  1880 
       
  1881     gst_structure_get_fourcc (structure, "format", &fourcc);
       
  1882     entry.fourcc = fourcc;
       
  1883     qtpad->is_out_of_order = TRUE;
       
  1884     qtpad->have_dts = TRUE;
       
  1885   } else if (strcmp (mimetype, "video/x-mp4-part") == 0) {
       
  1886     guint32 fourcc;
       
  1887 
       
  1888     gst_structure_get_fourcc (structure, "format", &fourcc);
       
  1889     entry.fourcc = fourcc;
       
  1890     qtpad->is_out_of_order = TRUE;
       
  1891     qtpad->have_dts = TRUE;
       
  1892   }
       
  1893 
       
  1894   if (!entry.fourcc)
       
  1895     goto refuse_caps;
       
  1896 
       
  1897   /* ok, set the pad info accordingly */
       
  1898   qtpad->fourcc = entry.fourcc;
       
  1899   qtpad->sync = sync;
       
  1900   atom_trak_set_video_type (qtpad->trak, qtmux->context, &entry, rate,
       
  1901       ext_atom);
       
  1902 
       
  1903   gst_object_unref (qtmux);
       
  1904   return TRUE;
       
  1905 
       
  1906   /* ERRORS */
       
  1907 refuse_caps:
       
  1908   {
       
  1909     GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
       
  1910         GST_PAD_NAME (pad), caps);
       
  1911     gst_object_unref (qtmux);
       
  1912     return FALSE;
       
  1913   }
       
  1914 refuse_renegotiation:
       
  1915   {
       
  1916     GST_WARNING_OBJECT (qtmux,
       
  1917         "pad %s refused renegotiation to %" GST_PTR_FORMAT " from %"
       
  1918         GST_PTR_FORMAT, GST_PAD_NAME (pad), caps, GST_PAD_CAPS (pad));
       
  1919     gst_object_unref (qtmux);
       
  1920     return FALSE;
       
  1921   }
       
  1922 }
       
  1923 
       
  1924 static gboolean
       
  1925 gst_qt_mux_sink_event (GstPad * pad, GstEvent * event)
       
  1926 {
       
  1927   gboolean ret;
       
  1928   GstQTMux *qtmux;
       
  1929 
       
  1930   qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
       
  1931   switch (GST_EVENT_TYPE (event)) {
       
  1932     case GST_EVENT_TAG:{
       
  1933       GstTagList *list;
       
  1934       GstTagSetter *setter = GST_TAG_SETTER (qtmux);
       
  1935       const GstTagMergeMode mode = gst_tag_setter_get_tag_merge_mode (setter);
       
  1936 
       
  1937       GST_DEBUG_OBJECT (qtmux, "received tag event");
       
  1938       gst_event_parse_tag (event, &list);
       
  1939       gst_tag_setter_merge_tags (setter, list, mode);
       
  1940       break;
       
  1941     }
       
  1942     default:
       
  1943       break;
       
  1944   }
       
  1945 
       
  1946   ret = qtmux->collect_event (pad, event);
       
  1947   gst_object_unref (qtmux);
       
  1948 
       
  1949   return ret;
       
  1950 }
       
  1951 
       
  1952 static void
       
  1953 gst_qt_mux_release_pad (GstElement * element, GstPad * pad)
       
  1954 {
       
  1955   GstQTMux *mux = GST_QT_MUX_CAST (element);
       
  1956 
       
  1957   /* let GstCollectPads complain if it is some unknown pad */
       
  1958   if (gst_collect_pads_remove_pad (mux->collect, pad))
       
  1959     gst_element_remove_pad (element, pad);
       
  1960 }
       
  1961 
       
  1962 static GstPad *
       
  1963 gst_qt_mux_request_new_pad (GstElement * element,
       
  1964     GstPadTemplate * templ, const gchar * req_name)
       
  1965 {
       
  1966   GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
       
  1967   GstQTMux *qtmux = GST_QT_MUX_CAST (element);
       
  1968   GstQTPad *collect_pad;
       
  1969   GstPad *newpad;
       
  1970   gboolean audio;
       
  1971   gchar *name;
       
  1972 
       
  1973   if (templ->direction != GST_PAD_SINK)
       
  1974     goto wrong_direction;
       
  1975 
       
  1976 //  if (qtmux->state != GST_QT_MUX_STATE_NONE)
       
  1977 //    goto too_late;
       
  1978 
       
  1979   if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) {
       
  1980     audio = TRUE;
       
  1981     name = g_strdup_printf ("audio_%02d", qtmux->audio_pads++);
       
  1982   } else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) {
       
  1983     audio = FALSE;
       
  1984     name = g_strdup_printf ("video_%02d", qtmux->video_pads++);
       
  1985   } else
       
  1986     goto wrong_template;
       
  1987 
       
  1988   GST_DEBUG_OBJECT (qtmux, "Requested pad: %s", name);
       
  1989 
       
  1990   /* create pad and add to collections */
       
  1991   newpad = gst_pad_new_from_template (templ, name);
       
  1992   g_free (name);
       
  1993   collect_pad = (GstQTPad *)
       
  1994       gst_collect_pads_add_pad_full (qtmux->collect, newpad, sizeof (GstQTPad),
       
  1995       (GstCollectDataDestroyNotify) (gst_qt_mux_pad_reset));
       
  1996   /* set up pad */
       
  1997   gst_qt_mux_pad_reset (collect_pad);
       
  1998   collect_pad->trak = atom_trak_new (qtmux->context);
       
  1999   atom_moov_add_trak (qtmux->moov, collect_pad->trak);
       
  2000 
       
  2001   /* set up pad functions */
       
  2002   if (audio)
       
  2003     gst_pad_set_setcaps_function (newpad,
       
  2004         GST_DEBUG_FUNCPTR (gst_qt_mux_audio_sink_set_caps));
       
  2005   else
       
  2006     gst_pad_set_setcaps_function (newpad,
       
  2007         GST_DEBUG_FUNCPTR (gst_qt_mux_video_sink_set_caps));
       
  2008 
       
  2009   /* FIXME: hacked way to override/extend the event function of
       
  2010    * GstCollectPads; because it sets its own event function giving the
       
  2011    * element no access to events.
       
  2012    */
       
  2013   qtmux->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (newpad);
       
  2014   gst_pad_set_event_function (newpad,
       
  2015       GST_DEBUG_FUNCPTR (gst_qt_mux_sink_event));
       
  2016 
       
  2017   gst_pad_set_active (newpad, TRUE);
       
  2018   gst_element_add_pad (element, newpad);
       
  2019 
       
  2020   return newpad;
       
  2021 
       
  2022   /* ERRORS */
       
  2023 wrong_direction:
       
  2024   {
       
  2025     GST_WARNING_OBJECT (qtmux, "Request pad that is not a SINK pad.");
       
  2026     return NULL;
       
  2027   }
       
  2028 too_late:
       
  2029   {
       
  2030     GST_WARNING_OBJECT (qtmux, "Not providing request pad after stream start.");
       
  2031     return NULL;
       
  2032   }
       
  2033 wrong_template:
       
  2034   {
       
  2035     GST_WARNING_OBJECT (qtmux, "This is not our template!");
       
  2036     return NULL;
       
  2037   }
       
  2038 }
       
  2039 
       
  2040 static void
       
  2041 gst_qt_mux_get_property (GObject * object,
       
  2042     guint prop_id, GValue * value, GParamSpec * pspec)
       
  2043 {
       
  2044   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
       
  2045 
       
  2046   GST_OBJECT_LOCK (qtmux);
       
  2047   switch (prop_id) {
       
  2048     case PROP_LARGE_FILE:
       
  2049       g_value_set_boolean (value, qtmux->large_file);
       
  2050       break;
       
  2051     case PROP_MOVIE_TIMESCALE:
       
  2052       g_value_set_uint (value, qtmux->timescale);
       
  2053       break;
       
  2054     case PROP_DO_CTTS:
       
  2055       g_value_set_boolean (value, qtmux->guess_pts);
       
  2056       break;
       
  2057     case PROP_FAST_START:
       
  2058       g_value_set_boolean (value, qtmux->fast_start);
       
  2059       break;
       
  2060     case PROP_FAST_START_TEMP_FILE:
       
  2061       g_value_set_string (value, qtmux->fast_start_file_path);
       
  2062       break;
       
  2063     default:
       
  2064       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       
  2065       break;
       
  2066   }
       
  2067   GST_OBJECT_UNLOCK (qtmux);
       
  2068 }
       
  2069 
       
  2070 static void
       
  2071 gst_qt_mux_generate_fast_start_file_path (GstQTMux * qtmux)
       
  2072 {
       
  2073   gchar *tmp;
       
  2074 
       
  2075   if (qtmux->fast_start_file_path) {
       
  2076     g_free (qtmux->fast_start_file_path);
       
  2077     qtmux->fast_start_file_path = NULL;
       
  2078   }
       
  2079 
       
  2080   tmp = g_strdup_printf ("%s%d", "qtmux", g_random_int ());
       
  2081   qtmux->fast_start_file_path = g_build_filename (g_get_tmp_dir (), tmp, NULL);
       
  2082   g_free (tmp);
       
  2083 }
       
  2084 
       
  2085 static void
       
  2086 gst_qt_mux_set_property (GObject * object,
       
  2087     guint prop_id, const GValue * value, GParamSpec * pspec)
       
  2088 {
       
  2089   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
       
  2090 
       
  2091   GST_OBJECT_LOCK (qtmux);
       
  2092   switch (prop_id) {
       
  2093     case PROP_LARGE_FILE:
       
  2094       qtmux->large_file = g_value_get_boolean (value);
       
  2095       break;
       
  2096     case PROP_MOVIE_TIMESCALE:
       
  2097       qtmux->timescale = g_value_get_uint (value);
       
  2098       break;
       
  2099     case PROP_DO_CTTS:
       
  2100       qtmux->guess_pts = g_value_get_boolean (value);
       
  2101       break;
       
  2102     case PROP_FAST_START:
       
  2103       qtmux->fast_start = g_value_get_boolean (value);
       
  2104       break;
       
  2105     case PROP_FAST_START_TEMP_FILE:
       
  2106       if (qtmux->fast_start_file_path) {
       
  2107         g_free (qtmux->fast_start_file_path);
       
  2108       }
       
  2109       qtmux->fast_start_file_path = g_value_dup_string (value);
       
  2110       /* NULL means to generate a random one */
       
  2111       if (!qtmux->fast_start_file_path) {
       
  2112         gst_qt_mux_generate_fast_start_file_path (qtmux);
       
  2113       }
       
  2114       break;
       
  2115     default:
       
  2116       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       
  2117       break;
       
  2118   }
       
  2119   GST_OBJECT_UNLOCK (qtmux);
       
  2120 }
       
  2121 
       
  2122 static GstStateChangeReturn
       
  2123 gst_qt_mux_change_state (GstElement * element, GstStateChange transition)
       
  2124 {
       
  2125   GstStateChangeReturn ret;
       
  2126   GstQTMux *qtmux = GST_QT_MUX_CAST (element);
       
  2127 
       
  2128   switch (transition) {
       
  2129     case GST_STATE_CHANGE_NULL_TO_READY:
       
  2130       break;
       
  2131     case GST_STATE_CHANGE_READY_TO_PAUSED:
       
  2132       gst_collect_pads_start (qtmux->collect);
       
  2133       qtmux->state = GST_QT_MUX_STATE_STARTED;
       
  2134       break;
       
  2135     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
       
  2136       break;
       
  2137     case GST_STATE_CHANGE_PAUSED_TO_READY:
       
  2138       gst_collect_pads_stop (qtmux->collect);
       
  2139       break;
       
  2140     default:
       
  2141       break;
       
  2142   }
       
  2143 
       
  2144   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
       
  2145 
       
  2146   switch (transition) {
       
  2147     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
       
  2148       break;
       
  2149     case GST_STATE_CHANGE_PAUSED_TO_READY:
       
  2150       gst_qt_mux_reset (qtmux, TRUE);
       
  2151       break;
       
  2152     case GST_STATE_CHANGE_READY_TO_NULL:
       
  2153       break;
       
  2154     default:
       
  2155       break;
       
  2156   }
       
  2157 
       
  2158   return ret;
       
  2159 }
       
  2160 
       
  2161 
       
  2162 gboolean
       
  2163 gst_qt_mux_register (GstPlugin * plugin)
       
  2164 {
       
  2165   GTypeInfo typeinfo = {
       
  2166     sizeof (GstQTMuxClass),
       
  2167     (GBaseInitFunc) gst_qt_mux_base_init,
       
  2168     NULL,
       
  2169     (GClassInitFunc) gst_qt_mux_class_init,
       
  2170     NULL,
       
  2171     NULL,
       
  2172     sizeof (GstQTMux),
       
  2173     0,
       
  2174     (GInstanceInitFunc) gst_qt_mux_init,
       
  2175   };
       
  2176   static const GInterfaceInfo tag_setter_info = {
       
  2177     NULL, NULL, NULL
       
  2178   };
       
  2179   GType type;
       
  2180   GstQTMuxFormat format;
       
  2181   GstQTMuxClassParams *params;
       
  2182   guint i = 0;
       
  2183 
       
  2184   GST_LOG ("Registering muxers");
       
  2185 
       
  2186   while (TRUE) {
       
  2187     GstQTMuxFormatProp *prop;
       
  2188 
       
  2189     prop = &gst_qt_mux_format_list[i];
       
  2190     format = prop->format;
       
  2191     if (format == GST_QT_MUX_FORMAT_NONE)
       
  2192       break;
       
  2193 
       
  2194     /* create a cache for these properties */
       
  2195     params = g_new0 (GstQTMuxClassParams, 1);
       
  2196     params->prop = prop;
       
  2197     params->src_caps = gst_static_caps_get (&prop->src_caps);
       
  2198     params->video_sink_caps = gst_static_caps_get (&prop->video_sink_caps);
       
  2199     params->audio_sink_caps = gst_static_caps_get (&prop->audio_sink_caps);
       
  2200 
       
  2201     /* create the type now */
       
  2202     type = g_type_register_static (GST_TYPE_ELEMENT, prop->type_name, &typeinfo,
       
  2203         0);
       
  2204     g_type_set_qdata (type, GST_QT_MUX_PARAMS_QDATA, (gpointer) params);
       
  2205     g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, &tag_setter_info);
       
  2206 
       
  2207     if (!gst_element_register (plugin, prop->name, GST_RANK_PRIMARY, type))
       
  2208       return FALSE;
       
  2209 
       
  2210     i++;
       
  2211   }
       
  2212 
       
  2213   GST_LOG ("Finished registering muxers");
       
  2214 
       
  2215   return TRUE;
       
  2216 }
       
  2217 
       
  2218 gboolean
       
  2219 gst_qt_mux_plugin_init (GstPlugin * plugin)
       
  2220 {
       
  2221   GST_DEBUG_CATEGORY_INIT (gst_qt_mux_debug, "qtmux", 0, "QT Muxer");
       
  2222 
       
  2223   return gst_qt_mux_register (plugin);
       
  2224 }
       
  2225 
       
  2226 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
       
  2227     GST_VERSION_MINOR,
       
  2228     "qtmux",
       
  2229     "Quicktime Muxer plugin",
       
  2230     gst_qt_mux_plugin_init, VERSION, "LGPL", "gsoc2008 package",
       
  2231     "embedded.ufcg.edu.br")
       
  2232 
       
  2233 EXPORT_C GstPluginDesc* _GST_PLUGIN_DESC()
       
  2234        {
       
  2235           return &gst_plugin_desc;
       
  2236        }