• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Quicktime muxer plugin for GStreamer
2  * Copyright (C) 2008-2010 Thiago Santos <thiagoss@embedded.ufcg.edu.br>
3  * Copyright (C) 2008 Mark Nauwelaerts <mnauw@users.sf.net>
4  * Copyright (C) 2010 Nokia Corporation. All rights reserved.
5  * Copyright (C) 2014 Jan Schmidt <jan@centricular.com>
6  * Contact: Stefan Kost <stefan.kost@nokia.com>
7 
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23 /*
24  * Unless otherwise indicated, Source Code is licensed under MIT license.
25  * See further explanation attached in License Statement (distributed in the file
26  * LICENSE).
27  *
28  * Permission is hereby granted, free of charge, to any person obtaining a copy of
29  * this software and associated documentation files (the "Software"), to deal in
30  * the Software without restriction, including without limitation the rights to
31  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
32  * of the Software, and to permit persons to whom the Software is furnished to do
33  * so, subject to the following conditions:
34  *
35  * The above copyright notice and this permission notice shall be included in all
36  * copies or substantial portions of the Software.
37  *
38  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
39  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
40  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
41  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
42  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
43  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
44  * SOFTWARE.
45  */
46 
47 
48 /**
49  * SECTION:GstQTMux
50  * @title: GstQTMux
51  * @short_description: Muxer for ISO MP4-based files
52  */
53 
54 /*
55  * Based on avimux
56  */
57 
58 #ifdef HAVE_CONFIG_H
59 #include "config.h"
60 #endif
61 
62 #include <glib/gstdio.h>
63 
64 #include <gst/gst.h>
65 #include <gst/base/gstbytereader.h>
66 #include <gst/base/gstbitreader.h>
67 #include <gst/audio/audio.h>
68 #include <gst/video/video.h>
69 #include <gst/tag/tag.h>
70 #include <gst/pbutils/pbutils.h>
71 
72 #include <sys/types.h>
73 #ifdef G_OS_WIN32
74 #include <io.h>                 /* lseek, open, close, read */
75 #undef lseek
76 #define lseek _lseeki64
77 #undef off_t
78 #define off_t guint64
79 #endif
80 
81 #ifdef _MSC_VER
82 #define ftruncate g_win32_ftruncate
83 #endif
84 
85 #ifdef HAVE_UNISTD_H
86 #  include <unistd.h>
87 #endif
88 
89 #include "gstisomp4elements.h"
90 #include "gstqtmux.h"
91 
92 GST_DEBUG_CATEGORY_STATIC (gst_qt_mux_debug);
93 #define GST_CAT_DEFAULT gst_qt_mux_debug
94 
95 #ifndef ABSDIFF
96 #define ABSDIFF(a, b) ((a) > (b) ? (a) - (b) : (b) - (a))
97 #endif
98 
99 /* Hacker notes.
100  *
101  * The basic building blocks of MP4 files are:
102  *  - an 'ftyp' box at the very start
103  *  - an 'mdat' box which contains the raw audio/video/subtitle data;
104  *    this is just a bunch of bytes, completely unframed and possibly
105  *    unordered with no additional meta-information
106  *  - a 'moov' box that contains information about the different streams
107  *    and what they contain, as well as sample tables for each stream
108  *    that tell the demuxer where in the mdat box each buffer/sample is
109  *    and what its duration/timestamp etc. is, and whether it's a
110  *    keyframe etc.
111  * Additionally, fragmented MP4 works by writing chunks of data in
112  * pairs of 'moof' and 'mdat' boxes:
113  *  - 'moof' boxes, header preceding each mdat fragment describing the
114  *    contents, like a moov but only for that fragment.
115  *  - a 'mfra' box for Fragmented MP4, which is written at the end and
116  *    contains a summary of all fragments and seek tables.
117  *
118  * Currently mp4mux can work in 4 different modes / generate 4 types
119  * of output files/streams:
120  *
121  * - Normal mp4: mp4mux will write a little ftyp identifier at the
122  *   beginning, then start an mdat box into which it will write all the
123  *   sample data. At EOS it will then write the moov header with track
124  *   headers and sample tables at the end of the file, and rewrite the
125  *   start of the file to fix up the mdat box size at the beginning.
126  *   It has to wait for EOS to write the moov (which includes the
127  *   sample tables) because it doesn't know how much space those
128  *   tables will be. The output downstream must be seekable to rewrite
129  *   the mdat box at EOS.
130  *
131  * - Fragmented mp4: moov header with track headers at start
132  *   but no sample table, followed by N fragments, each containing
133  *   track headers with sample tables followed by some data. Downstream
134  *   does not need to be seekable if the 'streamable' flag is TRUE,
135  *   as the final mfra and total duration will be omitted.
136  *
137  * - Fast-start mp4: the goal here is to create a file where the moov
138  *   headers are at the beginning; what mp4mux will do is write all
139  *   sample data into a temp file and build moov header plus sample
140  *   tables in memory and then when EOS comes, it will push out the
141  *   moov header plus sample tables at the beginning, followed by the
142  *   mdat sample data at the end which is read in from the temp file
143  *   Files created in this mode are better for streaming over the
144  *   network, since the client doesn't have to seek to the end of the
145  *   file to get the headers, but it requires copying all sample data
146  *   out of the temp file at EOS, which can be expensive. Downstream does
147  *   not need to be seekable, because of the use of the temp file.
148  *
149  * - Robust Muxing mode: In this mode, qtmux uses the reserved-max-duration
150  *   and reserved-moov-update-period properties to reserve free space
151  *   at the start of the file and periodically write the MOOV atom out
152  *   to it. That means that killing the muxing at any point still
153  *   results in a playable file, at the cost of wasting some amount of
154  *   free space at the start of file. The approximate recording duration
155  *   has to be known in advance to estimate how much free space to reserve
156  *   for the moov, and the downstream must be seekable.
157  *   If the moov header grows larger than the reserved space, an error
158  *   is generated - so it's better to over-estimate the amount of space
159  *   to reserve. To ensure the file is playable at any point, the moov
160  *   is updated using a 'ping-pong' strategy, so the output is never in
161  *   an invalid state.
162  */
163 
164 #ifndef GST_REMOVE_DEPRECATED
165 enum
166 {
167   DTS_METHOD_DD,
168   DTS_METHOD_REORDER,
169   DTS_METHOD_ASC
170 };
171 
172 static GType
gst_qt_mux_dts_method_get_type(void)173 gst_qt_mux_dts_method_get_type (void)
174 {
175   static GType gst_qt_mux_dts_method = 0;
176 
177   if (!gst_qt_mux_dts_method) {
178     static const GEnumValue dts_methods[] = {
179       {DTS_METHOD_DD, "delta/duration", "dd"},
180       {DTS_METHOD_REORDER, "reorder", "reorder"},
181       {DTS_METHOD_ASC, "ascending", "asc"},
182       {0, NULL, NULL},
183     };
184 
185     gst_qt_mux_dts_method =
186         g_enum_register_static ("GstQTMuxDtsMethods", dts_methods);
187   }
188 
189   return gst_qt_mux_dts_method;
190 }
191 
192 #define GST_TYPE_QT_MUX_DTS_METHOD \
193   (gst_qt_mux_dts_method_get_type ())
194 #endif
195 
196 static GType
gst_qt_mux_fragment_mode_get_type(void)197 gst_qt_mux_fragment_mode_get_type (void)
198 {
199   static GType gst_qt_mux_fragment_mode = 0;
200 
201   if (!gst_qt_mux_fragment_mode) {
202     static const GEnumValue gst_qt_mux_fragment_modes[] = {
203       {GST_QT_MUX_FRAGMENT_DASH_OR_MSS, "Dash or Smoothstreaming",
204           "dash-or-mss"},
205       {GST_QT_MUX_FRAGMENT_FIRST_MOOV_THEN_FINALISE,
206           "First MOOV Fragment Then Finalise", "first-moov-then-finalise"},
207       /* internal only */
208       /* {GST_QT_MUX_FRAGMENT_STREAMABLE, "streamable", "Streamable (ISML only.  Deprecated elsewhere)"}, */
209       {0, NULL, NULL},
210     };
211 
212     gst_qt_mux_fragment_mode =
213         g_enum_register_static ("GstQTMuxFragmentMode",
214         gst_qt_mux_fragment_modes);
215   }
216 
217   return gst_qt_mux_fragment_mode;
218 }
219 
220 #define GST_TYPE_QT_MUX_FRAGMENT_MODE \
221   (gst_qt_mux_fragment_mode_get_type ())
222 
223 enum
224 {
225   PROP_PAD_0,
226   PROP_PAD_TRAK_TIMESCALE,
227 };
228 
229 #define DEFAULT_PAD_TRAK_TIMESCALE          0
230 
231 G_DEFINE_TYPE (GstQTMuxPad, gst_qt_mux_pad, GST_TYPE_AGGREGATOR_PAD);
232 
233 static void
gst_qt_mux_pad_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)234 gst_qt_mux_pad_set_property (GObject * object,
235     guint prop_id, const GValue * value, GParamSpec * pspec)
236 {
237   GstQTMuxPad *pad = GST_QT_MUX_PAD_CAST (object);
238 
239   GST_OBJECT_LOCK (pad);
240   switch (prop_id) {
241     case PROP_PAD_TRAK_TIMESCALE:
242       pad->trak_timescale = g_value_get_uint (value);
243       break;
244     default:
245       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
246       break;
247   }
248   GST_OBJECT_UNLOCK (pad);
249 }
250 
251 static void
gst_qt_mux_pad_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)252 gst_qt_mux_pad_get_property (GObject * object,
253     guint prop_id, GValue * value, GParamSpec * pspec)
254 {
255   GstQTMuxPad *pad = GST_QT_MUX_PAD_CAST (object);
256 
257   GST_OBJECT_LOCK (pad);
258   switch (prop_id) {
259     case PROP_PAD_TRAK_TIMESCALE:
260       g_value_set_uint (value, pad->trak_timescale);
261       break;
262     default:
263       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
264       break;
265   }
266   GST_OBJECT_UNLOCK (pad);
267 }
268 
269 static void
gst_qt_mux_pad_class_init(GstQTMuxPadClass * klass)270 gst_qt_mux_pad_class_init (GstQTMuxPadClass * klass)
271 {
272   GObjectClass *gobject_class = (GObjectClass *) klass;
273 
274   gobject_class->get_property = gst_qt_mux_pad_get_property;
275   gobject_class->set_property = gst_qt_mux_pad_set_property;
276 
277   g_object_class_install_property (gobject_class, PROP_PAD_TRAK_TIMESCALE,
278       g_param_spec_uint ("trak-timescale", "Track timescale",
279           "Timescale to use for this pad's trak (units per second, 0 is automatic)",
280           0, G_MAXUINT32, DEFAULT_PAD_TRAK_TIMESCALE,
281           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
282 }
283 
284 static void
gst_qt_mux_pad_init(GstQTMuxPad * pad)285 gst_qt_mux_pad_init (GstQTMuxPad * pad)
286 {
287   pad->trak_timescale = DEFAULT_PAD_TRAK_TIMESCALE;
288 }
289 
290 static guint32
gst_qt_mux_pad_get_timescale(GstQTMuxPad * pad)291 gst_qt_mux_pad_get_timescale (GstQTMuxPad * pad)
292 {
293   guint32 timescale;
294 
295   GST_OBJECT_LOCK (pad);
296   timescale = pad->trak_timescale;
297   GST_OBJECT_UNLOCK (pad);
298 
299   return timescale;
300 }
301 
302 /* QTMux signals and args */
303 enum
304 {
305   /* FILL ME */
306   LAST_SIGNAL
307 };
308 
309 enum
310 {
311   PROP_0,
312   PROP_MOVIE_TIMESCALE,
313   PROP_TRAK_TIMESCALE,
314   PROP_FAST_START,
315   PROP_FAST_START_TEMP_FILE,
316   PROP_MOOV_RECOV_FILE,
317   PROP_FRAGMENT_DURATION,
318   PROP_RESERVED_MAX_DURATION,
319   PROP_RESERVED_DURATION_REMAINING,
320   PROP_RESERVED_MOOV_UPDATE_PERIOD,
321   PROP_RESERVED_BYTES_PER_SEC,
322   PROP_RESERVED_PREFILL,
323 #ifndef GST_REMOVE_DEPRECATED
324   PROP_DTS_METHOD,
325 #endif
326   PROP_DO_CTTS,
327   PROP_INTERLEAVE_BYTES,
328   PROP_INTERLEAVE_TIME,
329   PROP_FORCE_CHUNKS,
330   PROP_MAX_RAW_AUDIO_DRIFT,
331   PROP_START_GAP_THRESHOLD,
332   PROP_FORCE_CREATE_TIMECODE_TRAK,
333 /* ohos.ext.func.0016
334  * add additional features to set geographic location information in mp4 file.
335  * PROP_SET_LATITUDE is the property to set latitude.
336  * PROP_SET_LONGITUDE is the property to set longitude.
337  */
338 #ifdef OHOS_EXT_FUNC
339   PROP_SET_LATITUDE,
340   PROP_SET_LONGITUDE,
341 #endif
342 /* ohos.ext.func.0018
343  * add additional features to set orientationHint in mp4 file.
344  * PROP_ROTAION_ANGLE is the angle to set, must be{0, 90, 180, 270}.
345  */
346 #ifdef OHOS_EXT_FUNC
347   PROP_ROTAION_ANGLE,
348 #endif
349   PROP_FRAGMENT_MODE,
350 };
351 
352 /* some spare for header size as well */
353 #define MDAT_LARGE_FILE_LIMIT           ((guint64) 1024 * 1024 * 1024 * 2)
354 
355 #define DEFAULT_MOVIE_TIMESCALE         0
356 #define DEFAULT_TRAK_TIMESCALE          0
357 #define DEFAULT_DO_CTTS                 TRUE
358 #define DEFAULT_FAST_START              FALSE
359 #define DEFAULT_FAST_START_TEMP_FILE    NULL
360 #define DEFAULT_MOOV_RECOV_FILE         NULL
361 #define DEFAULT_FRAGMENT_DURATION       0
362 #define DEFAULT_STREAMABLE              TRUE
363 #ifndef GST_REMOVE_DEPRECATED
364 #define DEFAULT_DTS_METHOD              DTS_METHOD_REORDER
365 #endif
366 #define DEFAULT_RESERVED_MAX_DURATION   GST_CLOCK_TIME_NONE
367 #define DEFAULT_RESERVED_MOOV_UPDATE_PERIOD   GST_CLOCK_TIME_NONE
368 #define DEFAULT_RESERVED_BYTES_PER_SEC_PER_TRAK 550
369 #define DEFAULT_RESERVED_PREFILL FALSE
370 #define DEFAULT_INTERLEAVE_BYTES 0
371 #define DEFAULT_INTERLEAVE_TIME 250*GST_MSECOND
372 #define DEFAULT_FORCE_CHUNKS (FALSE)
373 #define DEFAULT_MAX_RAW_AUDIO_DRIFT 40 * GST_MSECOND
374 #define DEFAULT_START_GAP_THRESHOLD 0
375 #define DEFAULT_FORCE_CREATE_TIMECODE_TRAK FALSE
376 #define DEFAULT_FRAGMENT_MODE GST_QT_MUX_FRAGMENT_DASH_OR_MSS
377 
378 static void gst_qt_mux_finalize (GObject * object);
379 
380 /* property functions */
381 static void gst_qt_mux_set_property (GObject * object,
382     guint prop_id, const GValue * value, GParamSpec * pspec);
383 static void gst_qt_mux_get_property (GObject * object,
384     guint prop_id, GValue * value, GParamSpec * pspec);
385 
386 /* pad functions */
387 static GstPad *gst_qt_mux_request_new_pad (GstElement * element,
388     GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
389 static void gst_qt_mux_release_pad (GstElement * element, GstPad * pad);
390 
391 /* event */
392 static gboolean gst_qt_mux_sink_event (GstAggregator * agg,
393     GstAggregatorPad * agg_pad, GstEvent * event);
394 static GstFlowReturn gst_qt_mux_sink_event_pre_queue (GstAggregator * self,
395     GstAggregatorPad * aggpad, GstEvent * event);
396 
397 /* aggregator */
398 static GstAggregatorPad *gst_qt_mux_create_new_pad (GstAggregator * self,
399     GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps);
400 static GstFlowReturn gst_qt_mux_aggregate (GstAggregator * agg,
401     gboolean timeout);
402 static GstBuffer *gst_qt_mux_clip_running_time (GstAggregator * agg,
403     GstAggregatorPad * agg_pad, GstBuffer * buf);
404 static gboolean gst_qt_mux_start (GstAggregator * agg);
405 static gboolean gst_qt_mux_stop (GstAggregator * agg);
406 
407 /* internal */
408 
409 static GstFlowReturn gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTMuxPad * pad,
410     GstBuffer * buf);
411 
412 static GstFlowReturn
413 gst_qt_mux_robust_recording_rewrite_moov (GstQTMux * qtmux);
414 
415 static void gst_qt_mux_update_global_statistics (GstQTMux * qtmux);
416 static void gst_qt_mux_update_edit_lists (GstQTMux * qtmux);
417 
418 static GstFlowReturn gst_qtmux_push_mdat_stored_buffers (GstQTMux * qtmux);
419 
420 static GstElementClass *parent_class = NULL;
421 
422 static void
gst_qt_mux_base_init(gpointer g_class)423 gst_qt_mux_base_init (gpointer g_class)
424 {
425   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
426   GstQTMuxClass *klass = (GstQTMuxClass *) g_class;
427   GstQTMuxClassParams *params;
428   GstPadTemplate *videosinktempl, *audiosinktempl, *subtitlesinktempl,
429       *captionsinktempl;
430   GstPadTemplate *srctempl;
431   gchar *longname, *description;
432 
433   params =
434       (GstQTMuxClassParams *) g_type_get_qdata (G_OBJECT_CLASS_TYPE (g_class),
435       GST_QT_MUX_PARAMS_QDATA);
436   if (!params)
437     return;
438 
439   /* construct the element details struct */
440   longname = g_strdup_printf ("%s Muxer", params->prop->long_name);
441   description = g_strdup_printf ("Multiplex audio and video into a %s file",
442       params->prop->long_name);
443   gst_element_class_set_metadata (element_class, longname,
444       "Codec/Muxer", description,
445       "Thiago Sousa Santos <thiagoss@embedded.ufcg.edu.br>");
446   g_free (longname);
447   g_free (description);
448 
449   /* pad templates */
450   srctempl = gst_pad_template_new_with_gtype ("src", GST_PAD_SRC,
451       GST_PAD_ALWAYS, params->src_caps, GST_TYPE_AGGREGATOR_PAD);
452   gst_element_class_add_pad_template (element_class, srctempl);
453 
454   if (params->audio_sink_caps) {
455     audiosinktempl = gst_pad_template_new_with_gtype ("audio_%u",
456         GST_PAD_SINK, GST_PAD_REQUEST, params->audio_sink_caps,
457         GST_TYPE_QT_MUX_PAD);
458     gst_element_class_add_pad_template (element_class, audiosinktempl);
459   }
460 
461   if (params->video_sink_caps) {
462     videosinktempl = gst_pad_template_new_with_gtype ("video_%u",
463         GST_PAD_SINK, GST_PAD_REQUEST, params->video_sink_caps,
464         GST_TYPE_QT_MUX_PAD);
465     gst_element_class_add_pad_template (element_class, videosinktempl);
466   }
467 
468   if (params->subtitle_sink_caps) {
469     subtitlesinktempl = gst_pad_template_new_with_gtype ("subtitle_%u",
470         GST_PAD_SINK, GST_PAD_REQUEST, params->subtitle_sink_caps,
471         GST_TYPE_QT_MUX_PAD);
472     gst_element_class_add_pad_template (element_class, subtitlesinktempl);
473   }
474 
475   if (params->caption_sink_caps) {
476     captionsinktempl = gst_pad_template_new_with_gtype ("caption_%u",
477         GST_PAD_SINK, GST_PAD_REQUEST, params->caption_sink_caps,
478         GST_TYPE_QT_MUX_PAD);
479     gst_element_class_add_pad_template (element_class, captionsinktempl);
480   }
481 
482   klass->format = params->prop->format;
483 }
484 
485 static void
gst_qt_mux_class_init(GstQTMuxClass * klass)486 gst_qt_mux_class_init (GstQTMuxClass * klass)
487 {
488   GObjectClass *gobject_class;
489   GstElementClass *gstelement_class;
490   GstAggregatorClass *gstagg_class = GST_AGGREGATOR_CLASS (klass);
491 
492   gobject_class = (GObjectClass *) klass;
493   gstelement_class = (GstElementClass *) klass;
494 
495   parent_class = g_type_class_peek_parent (klass);
496 
497   gobject_class->finalize = gst_qt_mux_finalize;
498   gobject_class->get_property = gst_qt_mux_get_property;
499   gobject_class->set_property = gst_qt_mux_set_property;
500 
501   g_object_class_install_property (gobject_class, PROP_MOVIE_TIMESCALE,
502       g_param_spec_uint ("movie-timescale", "Movie timescale",
503           "Timescale to use in the movie (units per second, 0 == default)",
504           0, G_MAXUINT32, DEFAULT_MOVIE_TIMESCALE,
505           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
506   g_object_class_install_property (gobject_class, PROP_TRAK_TIMESCALE,
507       g_param_spec_uint ("trak-timescale", "Track timescale",
508           "Timescale to use for the tracks (units per second, 0 is automatic)",
509           0, G_MAXUINT32, DEFAULT_TRAK_TIMESCALE,
510           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
511   g_object_class_install_property (gobject_class, PROP_DO_CTTS,
512       g_param_spec_boolean ("presentation-time",
513           "Include presentation-time info",
514           "Calculate and include presentation/composition time "
515           "(in addition to decoding time)", DEFAULT_DO_CTTS,
516           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
517 #ifndef GST_REMOVE_DEPRECATED
518   g_object_class_install_property (gobject_class, PROP_DTS_METHOD,
519       g_param_spec_enum ("dts-method", "dts-method",
520           "Method to determine DTS time (DEPRECATED)",
521           GST_TYPE_QT_MUX_DTS_METHOD, DEFAULT_DTS_METHOD,
522           G_PARAM_DEPRECATED | G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
523           G_PARAM_STATIC_STRINGS));
524 #endif
525   g_object_class_install_property (gobject_class, PROP_FAST_START,
526       g_param_spec_boolean ("faststart", "Format file to faststart",
527           "If the file should be formatted for faststart (headers first)",
528           DEFAULT_FAST_START, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
529   g_object_class_install_property (gobject_class, PROP_FAST_START_TEMP_FILE,
530       g_param_spec_string ("faststart-file", "File to use for storing buffers",
531           "File that will be used temporarily to store data from the stream "
532           "when creating a faststart file. If null a filepath will be "
533           "created automatically", DEFAULT_FAST_START_TEMP_FILE,
534           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS |
535           GST_PARAM_DOC_SHOW_DEFAULT));
536   g_object_class_install_property (gobject_class, PROP_MOOV_RECOV_FILE,
537       g_param_spec_string ("moov-recovery-file",
538           "File to store data for posterior moov atom recovery",
539           "File to be used to store "
540           "data for moov atom making movie file recovery possible in case "
541           "of a crash during muxing. Null for disabled. (Experimental)",
542           DEFAULT_MOOV_RECOV_FILE,
543           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
544   g_object_class_install_property (gobject_class, PROP_FRAGMENT_DURATION,
545       g_param_spec_uint ("fragment-duration", "Fragment duration",
546           "Fragment durations in ms (produce a fragmented file if > 0)",
547           0, G_MAXUINT32, klass->format == GST_QT_MUX_FORMAT_ISML ?
548           2000 : DEFAULT_FRAGMENT_DURATION,
549           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
550   g_object_class_install_property (gobject_class, PROP_RESERVED_MAX_DURATION,
551       g_param_spec_uint64 ("reserved-max-duration",
552           "Reserved maximum file duration (ns)",
553           "When set to a value > 0, reserves space for index tables at the "
554           "beginning of the file.",
555           0, G_MAXUINT64, DEFAULT_RESERVED_MAX_DURATION,
556           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
557   g_object_class_install_property (gobject_class,
558       PROP_RESERVED_DURATION_REMAINING,
559       g_param_spec_uint64 ("reserved-duration-remaining",
560           "Report the approximate amount of remaining recording space (ns)",
561           "Reports the approximate amount of remaining moov header space "
562           "reserved using reserved-max-duration", 0, G_MAXUINT64, 0,
563           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
564   g_object_class_install_property (gobject_class,
565       PROP_RESERVED_MOOV_UPDATE_PERIOD,
566       g_param_spec_uint64 ("reserved-moov-update-period",
567           "Interval at which to update index tables (ns)",
568           "When used with reserved-max-duration, periodically updates the "
569           "index tables with information muxed so far.", 0, G_MAXUINT64,
570           DEFAULT_RESERVED_MOOV_UPDATE_PERIOD,
571           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
572   g_object_class_install_property (gobject_class, PROP_RESERVED_BYTES_PER_SEC,
573       g_param_spec_uint ("reserved-bytes-per-sec",
574           "Reserved MOOV bytes per second, per track",
575           "Multiplier for converting reserved-max-duration into bytes of header to reserve, per second, per track",
576           0, 10000, DEFAULT_RESERVED_BYTES_PER_SEC_PER_TRAK,
577           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
578   g_object_class_install_property (gobject_class, PROP_RESERVED_PREFILL,
579       g_param_spec_boolean ("reserved-prefill",
580           "Reserved Prefill Samples Table",
581           "Prefill samples table of reserved duration",
582           DEFAULT_RESERVED_PREFILL,
583           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
584   g_object_class_install_property (gobject_class, PROP_INTERLEAVE_BYTES,
585       g_param_spec_uint64 ("interleave-bytes", "Interleave (bytes)",
586           "Interleave between streams in bytes",
587           0, G_MAXUINT64, DEFAULT_INTERLEAVE_BYTES,
588           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
589   g_object_class_install_property (gobject_class, PROP_INTERLEAVE_TIME,
590       g_param_spec_uint64 ("interleave-time", "Interleave (time)",
591           "Interleave between streams in nanoseconds",
592           0, G_MAXUINT64, DEFAULT_INTERLEAVE_TIME,
593           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
594   g_object_class_install_property (gobject_class, PROP_FORCE_CHUNKS,
595       g_param_spec_boolean ("force-chunks", "Force Chunks",
596           "Force multiple chunks to be created even for single-stream files",
597           DEFAULT_FORCE_CHUNKS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
598   g_object_class_install_property (gobject_class, PROP_MAX_RAW_AUDIO_DRIFT,
599       g_param_spec_uint64 ("max-raw-audio-drift", "Max Raw Audio Drift",
600           "Maximum allowed drift of raw audio samples vs. timestamps in nanoseconds",
601           0, G_MAXUINT64, DEFAULT_MAX_RAW_AUDIO_DRIFT,
602           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
603   g_object_class_install_property (gobject_class, PROP_START_GAP_THRESHOLD,
604       g_param_spec_uint64 ("start-gap-threshold", "Start Gap Threshold",
605           "Threshold for creating an edit list for gaps at the start in nanoseconds",
606           0, G_MAXUINT64, DEFAULT_START_GAP_THRESHOLD,
607           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
608   g_object_class_install_property (gobject_class,
609       PROP_FORCE_CREATE_TIMECODE_TRAK,
610       g_param_spec_boolean ("force-create-timecode-trak",
611           "Force Create Timecode Trak",
612           "Create a timecode trak even in unsupported flavors",
613           DEFAULT_FORCE_CREATE_TIMECODE_TRAK,
614           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
615 
616   /**
617    * GstBaseQTMux:fragment-mode:
618    *
619    * Influence how fragmented files are produces.  Only has any affect when the
620    * the 'fragment-duration' property is set to a value greater than '0'
621    *
622    * Currently, two options exist:
623    * - "dash-or-mss": for the original fragmented mode that supports dash or
624    *   mocrosoft smoothstreaming with a single input stream
625    * - "first-moov-then-finalise" is a fragmented mode that will start with a
626    *   self-contained 'moov' atom fo the first fragment, then produce fragments.
627    *   When the file is finalised, the initial 'moov' is invalidated and a
628    *   new 'moov' is written covering the entire file.
629    *
630    * Since: 1.20
631    */
632   g_object_class_install_property (gobject_class, PROP_FRAGMENT_MODE,
633       g_param_spec_enum ("fragment-mode", "Fragment Mode",
634           "How to to write fragments to the file.  Only used when "
635           "\'fragment-duration\' is greather than 0",
636           GST_TYPE_QT_MUX_FRAGMENT_MODE, DEFAULT_FRAGMENT_MODE,
637           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
638 
639 /* ohos.ext.func.0016
640  * add additional features to set geographic location information in mp4 file.
641  * PROP_SET_LATITUDE is the property to set latitude.
642  * PROP_SET_LONGITUDE is the property to set longitude.
643  */
644 #ifdef OHOS_EXT_FUNC
645     g_object_class_install_property (gobject_class, PROP_SET_LATITUDE,
646         g_param_spec_int ("set-latitude", "Set Latitude",
647             "set the latitude in geolocation, here multiplying 10000",
648             G_MININT32, G_MAXINT32, 0,
649             G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
650     g_object_class_install_property (gobject_class, PROP_SET_LONGITUDE,
651         g_param_spec_int ("set-longitude", "Set Longitude",
652             "set the longitude in geolocation, here multiplying 10000",
653             G_MININT32, G_MAXINT32, 0,
654             G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
655 #endif
656 
657 /* ohos.ext.func.0018
658  * add additional features to set orientationHint in mp4 file.
659  * PROP_ROTAION_ANGLE is the angle to set, must be{0, 90, 180, 270}.
660  */
661 #ifdef OHOS_EXT_FUNC
662     g_object_class_install_property (gobject_class, PROP_ROTAION_ANGLE,
663         g_param_spec_uint ("orientation-hint", "Orientation Hint",
664             "set the rotation angle in mp4 file",
665             0, 270, 0,
666             G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
667 #endif
668 
669   gstelement_class->request_new_pad =
670       GST_DEBUG_FUNCPTR (gst_qt_mux_request_new_pad);
671   gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_qt_mux_release_pad);
672 
673   gstagg_class->sink_event = gst_qt_mux_sink_event;
674   gstagg_class->sink_event_pre_queue = gst_qt_mux_sink_event_pre_queue;
675   gstagg_class->aggregate = gst_qt_mux_aggregate;
676   gstagg_class->clip = gst_qt_mux_clip_running_time;
677   gstagg_class->start = gst_qt_mux_start;
678   gstagg_class->stop = gst_qt_mux_stop;
679   gstagg_class->create_new_pad = gst_qt_mux_create_new_pad;
680   gstagg_class->negotiate = NULL;
681 
682   gst_type_mark_as_plugin_api (GST_TYPE_QT_MUX_PAD, 0);
683   gst_type_mark_as_plugin_api (GST_TYPE_QT_MUX_DTS_METHOD, 0);
684   gst_type_mark_as_plugin_api (GST_TYPE_QT_MUX_FRAGMENT_MODE, 0);
685 }
686 
687 static void
gst_qt_mux_pad_reset(GstQTMuxPad * qtpad)688 gst_qt_mux_pad_reset (GstQTMuxPad * qtpad)
689 {
690   qtpad->fourcc = 0;
691   qtpad->is_out_of_order = FALSE;
692   qtpad->sample_size = 0;
693   qtpad->sync = FALSE;
694   qtpad->last_dts = 0;
695   qtpad->sample_offset = 0;
696   qtpad->dts_adjustment = GST_CLOCK_TIME_NONE;
697   qtpad->first_ts = GST_CLOCK_TIME_NONE;
698   qtpad->first_dts = GST_CLOCK_TIME_NONE;
699   qtpad->prepare_buf_func = NULL;
700   qtpad->create_empty_buffer = NULL;
701   qtpad->avg_bitrate = 0;
702   qtpad->max_bitrate = 0;
703   qtpad->total_duration = 0;
704   qtpad->total_bytes = 0;
705   qtpad->sparse = FALSE;
706   qtpad->first_cc_sample_size = 0;
707   qtpad->flow_status = GST_FLOW_OK;
708   qtpad->warned_empty_duration = FALSE;
709 
710   gst_buffer_replace (&qtpad->last_buf, NULL);
711 
712   gst_caps_replace (&qtpad->configured_caps, NULL);
713 
714   if (qtpad->tags) {
715     gst_tag_list_unref (qtpad->tags);
716     qtpad->tags = NULL;
717   }
718 
719   /* reference owned elsewhere */
720   qtpad->trak = NULL;
721   qtpad->tc_trak = NULL;
722 
723   if (qtpad->traf) {
724     atom_traf_free (qtpad->traf);
725     qtpad->traf = NULL;
726   }
727   atom_array_clear (&qtpad->fragment_buffers);
728   if (qtpad->samples)
729     g_array_unref (qtpad->samples);
730   qtpad->samples = NULL;
731 
732   /* reference owned elsewhere */
733   qtpad->tfra = NULL;
734 
735   qtpad->first_pts = GST_CLOCK_TIME_NONE;
736   qtpad->tc_pos = -1;
737   if (qtpad->first_tc)
738     gst_video_time_code_free (qtpad->first_tc);
739   qtpad->first_tc = NULL;
740 
741   if (qtpad->raw_audio_adapter)
742     gst_object_unref (qtpad->raw_audio_adapter);
743   qtpad->raw_audio_adapter = NULL;
744 }
745 
746 /*
747  * Takes GstQTMux back to its initial state
748  */
749 static void
gst_qt_mux_reset(GstQTMux * qtmux,gboolean alloc)750 gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc)
751 {
752   GSList *walk;
753   GList *l;
754 
755   qtmux->state = GST_QT_MUX_STATE_NONE;
756   qtmux->header_size = 0;
757   qtmux->mdat_size = 0;
758   qtmux->moov_pos = 0;
759   qtmux->mdat_pos = 0;
760   qtmux->longest_chunk = GST_CLOCK_TIME_NONE;
761   qtmux->fragment_sequence = 0;
762 
763   if (qtmux->ftyp) {
764     atom_ftyp_free (qtmux->ftyp);
765     qtmux->ftyp = NULL;
766   }
767   if (qtmux->moov) {
768     atom_moov_free (qtmux->moov);
769     qtmux->moov = NULL;
770   }
771   if (qtmux->mfra) {
772     atom_mfra_free (qtmux->mfra);
773     qtmux->mfra = NULL;
774   }
775   if (qtmux->fast_start_file) {
776     fclose (qtmux->fast_start_file);
777     g_remove (qtmux->fast_start_file_path);
778     qtmux->fast_start_file = NULL;
779   }
780   if (qtmux->moov_recov_file) {
781     fclose (qtmux->moov_recov_file);
782     qtmux->moov_recov_file = NULL;
783   }
784   for (walk = qtmux->extra_atoms; walk; walk = g_slist_next (walk)) {
785     AtomInfo *ainfo = (AtomInfo *) walk->data;
786     ainfo->free_func (ainfo->atom);
787     g_free (ainfo);
788   }
789   g_slist_free (qtmux->extra_atoms);
790   qtmux->extra_atoms = NULL;
791 
792   GST_OBJECT_LOCK (qtmux);
793   gst_tag_setter_reset_tags (GST_TAG_SETTER (qtmux));
794 
795   /* reset pad data */
796   for (l = GST_ELEMENT_CAST (qtmux)->sinkpads; l; l = l->next) {
797     GstQTMuxPad *qtpad = (GstQTMuxPad *) l->data;
798     gst_qt_mux_pad_reset (qtpad);
799 
800     /* hm, moov_free above yanked the traks away from us,
801      * so do not free, but do clear */
802     qtpad->trak = NULL;
803   }
804 
805   if (alloc) {
806     qtmux->moov = atom_moov_new (qtmux->context);
807     /* ensure all is as nice and fresh as request_new_pad would provide it */
808     for (l = GST_ELEMENT_CAST (qtmux)->sinkpads; l; l = l->next) {
809       GstQTMuxPad *qtpad = (GstQTMuxPad *) l->data;
810 
811       qtpad->trak = atom_trak_new (qtmux->context);
812       atom_moov_add_trak (qtmux->moov, qtpad->trak);
813     }
814   }
815   GST_OBJECT_UNLOCK (qtmux);
816 
817   g_list_free_full (qtmux->output_buffers, (GDestroyNotify) gst_buffer_unref);
818   qtmux->output_buffers = NULL;
819 
820   qtmux->current_pad = NULL;
821   qtmux->current_chunk_size = 0;
822   qtmux->current_chunk_duration = 0;
823   qtmux->current_chunk_offset = -1;
824 
825   qtmux->reserved_moov_size = 0;
826   qtmux->last_moov_update = GST_CLOCK_TIME_NONE;
827   qtmux->muxed_since_last_update = 0;
828   qtmux->reserved_duration_remaining = GST_CLOCK_TIME_NONE;
829 }
830 
831 static GstBuffer *
gst_qt_mux_clip_running_time(GstAggregator * agg,GstAggregatorPad * agg_pad,GstBuffer * buf)832 gst_qt_mux_clip_running_time (GstAggregator * agg,
833     GstAggregatorPad * agg_pad, GstBuffer * buf)
834 {
835   GstQTMuxPad *qtpad = GST_QT_MUX_PAD (agg_pad);
836   GstBuffer *outbuf = buf;
837 
838   /* invalid left alone and passed */
839   if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DTS_OR_PTS (buf)))) {
840     GstClockTime time;
841     GstClockTime buf_dts, abs_dts;
842     gint dts_sign;
843 
844     time = GST_BUFFER_PTS (buf);
845 
846     if (GST_CLOCK_TIME_IS_VALID (time)) {
847       time =
848           gst_segment_to_running_time (&agg_pad->segment, GST_FORMAT_TIME,
849           time);
850       if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (time))) {
851         GST_DEBUG_OBJECT (agg_pad, "clipping buffer on pad outside segment %"
852             GST_TIME_FORMAT, GST_TIME_ARGS (GST_BUFFER_PTS (buf)));
853         gst_buffer_unref (buf);
854         return NULL;
855       }
856     }
857 
858     GST_LOG_OBJECT (agg_pad, "buffer pts %" GST_TIME_FORMAT " -> %"
859         GST_TIME_FORMAT " running time",
860         GST_TIME_ARGS (GST_BUFFER_PTS (buf)), GST_TIME_ARGS (time));
861     outbuf = gst_buffer_make_writable (buf);
862     GST_BUFFER_PTS (outbuf) = time;
863 
864     dts_sign = gst_segment_to_running_time_full (&agg_pad->segment,
865         GST_FORMAT_TIME, GST_BUFFER_DTS (outbuf), &abs_dts);
866     buf_dts = GST_BUFFER_DTS (outbuf);
867     if (dts_sign > 0) {
868       GST_BUFFER_DTS (outbuf) = abs_dts;
869       qtpad->dts = abs_dts;
870     } else if (dts_sign < 0) {
871       GST_BUFFER_DTS (outbuf) = GST_CLOCK_TIME_NONE;
872       qtpad->dts = -((gint64) abs_dts);
873     } else {
874       GST_BUFFER_DTS (outbuf) = GST_CLOCK_TIME_NONE;
875       qtpad->dts = GST_CLOCK_STIME_NONE;
876     }
877 
878     GST_LOG_OBJECT (agg_pad, "buffer dts %" GST_TIME_FORMAT " -> %"
879         GST_STIME_FORMAT " running time", GST_TIME_ARGS (buf_dts),
880         GST_STIME_ARGS (qtpad->dts));
881   }
882 
883   return outbuf;
884 }
885 
886 static void
gst_qt_mux_init(GstQTMux * qtmux,GstQTMuxClass * qtmux_klass)887 gst_qt_mux_init (GstQTMux * qtmux, GstQTMuxClass * qtmux_klass)
888 {
889   /* properties set to default upon construction */
890 
891   qtmux->reserved_max_duration = DEFAULT_RESERVED_MAX_DURATION;
892   qtmux->reserved_moov_update_period = DEFAULT_RESERVED_MOOV_UPDATE_PERIOD;
893   qtmux->reserved_bytes_per_sec_per_trak =
894       DEFAULT_RESERVED_BYTES_PER_SEC_PER_TRAK;
895   qtmux->interleave_bytes = DEFAULT_INTERLEAVE_BYTES;
896   qtmux->interleave_time = DEFAULT_INTERLEAVE_TIME;
897   qtmux->force_chunks = DEFAULT_FORCE_CHUNKS;
898   qtmux->max_raw_audio_drift = DEFAULT_MAX_RAW_AUDIO_DRIFT;
899   qtmux->start_gap_threshold = DEFAULT_START_GAP_THRESHOLD;
900   qtmux->force_create_timecode_trak = DEFAULT_FORCE_CREATE_TIMECODE_TRAK;
901 
902 /* ohos.ext.func.0016
903  * add additional features to set geographic location information in mp4 file
904  * the feature is default disable.
905  */
906 #ifdef OHOS_EXT_FUNC
907   qtmux->enable_geolocation = FALSE;
908 #endif
909 
910 /* ohos.opt.compat.0011
911  * qtmux itself does not handle flush events, so in extreme cases, the buffer is discarded by gstpad
912  * when it is passed forward, but qtmux thinks that the buffer writes the file successfully,
913  * resulting in a file exception.
914  * is_flushing: a flag to tell qtmux, in flushing progress.
915  * flush_lock: is a lock to make sure the flag Operating normally.
916  */
917 #ifdef OHOS_OPT_COMPAT
918   g_mutex_init(&qtmux->flush_lock);
919   qtmux->is_flushing = FALSE;
920 #endif
921 
922   /* always need this */
923   qtmux->context =
924       atoms_context_new (gst_qt_mux_map_format_to_flavor (qtmux_klass->format),
925       qtmux->force_create_timecode_trak);
926 
927   /* internals to initial state */
928   gst_qt_mux_reset (qtmux, TRUE);
929 }
930 
931 
932 static void
gst_qt_mux_finalize(GObject * object)933 gst_qt_mux_finalize (GObject * object)
934 {
935   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
936 
937   gst_qt_mux_reset (qtmux, FALSE);
938 
939   g_free (qtmux->fast_start_file_path);
940   g_free (qtmux->moov_recov_file_path);
941 
942 /* ohos.opt.compat.0011
943  * qtmux itself does not handle flush events, so in extreme cases, the buffer is discarded by gstpad
944  * when it is passed forward, but qtmux thinks that the buffer writes the file successfully,
945  * resulting in a file exception.
946  * is_flushing: a flag to tell qtmux, in flushing progress.
947  * flush_lock: is a lock to make sure the flag Operating normally.
948  */
949 #ifdef OHOS_OPT_COMPAT
950   g_mutex_clear(&qtmux->flush_lock);
951 #endif
952 
953   atoms_context_free (qtmux->context);
954 
955   G_OBJECT_CLASS (parent_class)->finalize (object);
956 }
957 
958 static GstBuffer *
gst_qt_mux_prepare_jpc_buffer(GstQTMuxPad * qtpad,GstBuffer * buf,GstQTMux * qtmux)959 gst_qt_mux_prepare_jpc_buffer (GstQTMuxPad * qtpad, GstBuffer * buf,
960     GstQTMux * qtmux)
961 {
962   GstBuffer *newbuf;
963   GstMapInfo map;
964   gsize size;
965 
966   GST_LOG_OBJECT (qtmux, "Preparing jpc buffer");
967 
968   if (buf == NULL)
969     return NULL;
970 
971   size = gst_buffer_get_size (buf);
972   newbuf = gst_buffer_new_and_alloc (size + 8);
973   gst_buffer_copy_into (newbuf, buf, GST_BUFFER_COPY_ALL, 8, size);
974 
975   gst_buffer_map (newbuf, &map, GST_MAP_WRITE);
976   GST_WRITE_UINT32_BE (map.data, map.size);
977   GST_WRITE_UINT32_LE (map.data + 4, FOURCC_jp2c);
978 
979   gst_buffer_unmap (buf, &map);
980   gst_buffer_unref (buf);
981 
982   return newbuf;
983 }
984 
985 static gsize
extract_608_field_from_s334_1a(const guint8 * ccdata,gsize ccdata_size,guint field,guint8 ** res)986 extract_608_field_from_s334_1a (const guint8 * ccdata, gsize ccdata_size,
987     guint field, guint8 ** res)
988 {
989   guint8 *storage;
990   gsize storage_size = 128;
991   gsize i, res_size = 0;
992 
993   storage = g_malloc0 (storage_size);
994 
995   /* Iterate over the ccdata and put the corresponding tuples for the given field
996    * in the storage */
997   for (i = 0; i < ccdata_size; i += 3) {
998     if ((field == 1 && (ccdata[i * 3] & 0x80)) ||
999         (field == 2 && !(ccdata[i * 3] & 0x80))) {
1000       GST_DEBUG ("Storing matching cc for field %d : 0x%02x 0x%02x", field,
1001           ccdata[i * 3 + 1], ccdata[i * 3 + 2]);
1002       if (res_size >= storage_size) {
1003         storage_size += 128;
1004         storage = g_realloc (storage, storage_size);
1005       }
1006       storage[res_size] = ccdata[i * 3 + 1];
1007       storage[res_size + 1] = ccdata[i * 3 + 2];
1008       res_size += 2;
1009     }
1010   }
1011 
1012   if (res_size == 0) {
1013     g_free (storage);
1014     *res = NULL;
1015     return 0;
1016   }
1017 
1018   *res = storage;
1019   return res_size;
1020 }
1021 
1022 
1023 static GstBuffer *
gst_qt_mux_prepare_caption_buffer(GstQTMuxPad * qtpad,GstBuffer * buf,GstQTMux * qtmux)1024 gst_qt_mux_prepare_caption_buffer (GstQTMuxPad * qtpad, GstBuffer * buf,
1025     GstQTMux * qtmux)
1026 {
1027   GstBuffer *newbuf = NULL;
1028   GstMapInfo map, inmap;
1029   gsize size;
1030   gboolean in_prefill;
1031 
1032   if (buf == NULL)
1033     return NULL;
1034 
1035   in_prefill = (qtmux->mux_mode == GST_QT_MUX_MODE_ROBUST_RECORDING_PREFILL);
1036 
1037   size = gst_buffer_get_size (buf);
1038   gst_buffer_map (buf, &inmap, GST_MAP_READ);
1039 
1040   GST_LOG_OBJECT (qtmux,
1041       "Preparing caption buffer %" GST_FOURCC_FORMAT " size:%" G_GSIZE_FORMAT,
1042       GST_FOURCC_ARGS (qtpad->fourcc), size);
1043 
1044   switch (qtpad->fourcc) {
1045     case FOURCC_c608:
1046     {
1047       guint8 *cdat, *cdt2;
1048       gsize cdat_size, cdt2_size, total_size = 0;
1049       gsize write_offs = 0;
1050 
1051       cdat_size =
1052           extract_608_field_from_s334_1a (inmap.data, inmap.size, 1, &cdat);
1053       cdt2_size =
1054           extract_608_field_from_s334_1a (inmap.data, inmap.size, 2, &cdt2);
1055 
1056       if (cdat_size)
1057         total_size += cdat_size + 8;
1058       if (cdt2_size)
1059         total_size += cdt2_size + 8;
1060       if (total_size == 0) {
1061         GST_DEBUG_OBJECT (qtmux, "No 608 data ?");
1062         /* FIXME : We might want to *always* store something, even if
1063          * it's "empty" CC (i.e. 0x80 0x80) */
1064         break;
1065       }
1066 
1067       newbuf = gst_buffer_new_and_alloc (in_prefill ? 20 : total_size);
1068       /* Let's copy over all metadata and not the memory */
1069       gst_buffer_copy_into (newbuf, buf, GST_BUFFER_COPY_METADATA, 0, size);
1070 
1071       gst_buffer_map (newbuf, &map, GST_MAP_WRITE);
1072       if (cdat_size || in_prefill) {
1073         GST_WRITE_UINT32_BE (map.data, in_prefill ? 10 : cdat_size + 8);
1074         GST_WRITE_UINT32_LE (map.data + 4, FOURCC_cdat);
1075         if (cdat_size)
1076           memcpy (map.data + 8, cdat, in_prefill ? 2 : cdat_size);
1077         else {
1078           /* Write 'empty' CC */
1079           map.data[8] = 0x80;
1080           map.data[9] = 0x80;
1081         }
1082         write_offs = in_prefill ? 10 : cdat_size + 8;
1083         if (cdat_size)
1084           g_free (cdat);
1085       }
1086 
1087       if (cdt2_size || in_prefill) {
1088         GST_WRITE_UINT32_BE (map.data + write_offs,
1089             in_prefill ? 10 : cdt2_size + 8);
1090         GST_WRITE_UINT32_LE (map.data + write_offs + 4, FOURCC_cdt2);
1091         if (cdt2_size)
1092           memcpy (map.data + write_offs + 8, cdt2, in_prefill ? 2 : cdt2_size);
1093         else {
1094           /* Write 'empty' CC */
1095           map.data[write_offs + 8] = 0x80;
1096           map.data[write_offs + 9] = 0x80;
1097         }
1098         if (cdt2_size)
1099           g_free (cdt2);
1100       }
1101       gst_buffer_unmap (newbuf, &map);
1102       break;
1103     }
1104       break;
1105     case FOURCC_c708:
1106     {
1107       gsize actual_size;
1108 
1109       /* Take the whole CDP */
1110       if (in_prefill) {
1111         if (size > qtpad->first_cc_sample_size) {
1112           GST_ELEMENT_WARNING (qtmux, RESOURCE, WRITE,
1113               ("Truncating too big CEA708 sample (%" G_GSIZE_FORMAT " > %u)",
1114                   size, qtpad->first_cc_sample_size), (NULL));
1115         } else if (size < qtpad->first_cc_sample_size) {
1116           GST_ELEMENT_WARNING (qtmux, RESOURCE, WRITE,
1117               ("Padding too small CEA708 sample (%" G_GSIZE_FORMAT " < %u)",
1118                   size, qtpad->first_cc_sample_size), (NULL));
1119         }
1120 
1121         actual_size = MIN (qtpad->first_cc_sample_size, size);
1122       } else {
1123         actual_size = size;
1124       }
1125 
1126       newbuf = gst_buffer_new_and_alloc (actual_size + 8);
1127 
1128       /* Let's copy over all metadata and not the memory */
1129       gst_buffer_copy_into (newbuf, buf, GST_BUFFER_COPY_METADATA, 0, -1);
1130 
1131       gst_buffer_map (newbuf, &map, GST_MAP_WRITE);
1132 
1133       GST_WRITE_UINT32_BE (map.data, actual_size + 8);
1134       GST_WRITE_UINT32_LE (map.data + 4, FOURCC_ccdp);
1135       memcpy (map.data + 8, inmap.data, actual_size);
1136 
1137       gst_buffer_unmap (newbuf, &map);
1138       break;
1139     }
1140     default:
1141       /* theoretically this should never happen, but let's keep this here in case */
1142       GST_WARNING_OBJECT (qtmux, "Unknown caption format");
1143       break;
1144   }
1145 
1146   gst_buffer_unmap (buf, &inmap);
1147   gst_buffer_unref (buf);
1148 
1149   return newbuf;
1150 }
1151 
1152 static GstBuffer *
gst_qt_mux_prepare_tx3g_buffer(GstQTMuxPad * qtpad,GstBuffer * buf,GstQTMux * qtmux)1153 gst_qt_mux_prepare_tx3g_buffer (GstQTMuxPad * qtpad, GstBuffer * buf,
1154     GstQTMux * qtmux)
1155 {
1156   GstBuffer *newbuf;
1157   GstMapInfo frommap;
1158   GstMapInfo tomap;
1159   gsize size;
1160   const guint8 *dataend;
1161 
1162   GST_LOG_OBJECT (qtmux, "Preparing tx3g buffer %" GST_PTR_FORMAT, buf);
1163 
1164   if (buf == NULL)
1165     return NULL;
1166 
1167   gst_buffer_map (buf, &frommap, GST_MAP_READ);
1168 
1169   dataend = memchr (frommap.data, 0, frommap.size);
1170   size = dataend ? dataend - frommap.data : frommap.size;
1171   newbuf = gst_buffer_new_and_alloc (size + 2);
1172 
1173   gst_buffer_map (newbuf, &tomap, GST_MAP_WRITE);
1174 
1175   GST_WRITE_UINT16_BE (tomap.data, size);
1176   memcpy (tomap.data + 2, frommap.data, size);
1177 
1178   gst_buffer_unmap (newbuf, &tomap);
1179   gst_buffer_unmap (buf, &frommap);
1180 
1181   gst_buffer_copy_into (newbuf, buf, GST_BUFFER_COPY_METADATA, 0, size);
1182 
1183   /* gst_buffer_copy_into is trying to be too clever and
1184    * won't copy duration when size is different */
1185   GST_BUFFER_DURATION (newbuf) = GST_BUFFER_DURATION (buf);
1186 
1187   gst_buffer_unref (buf);
1188 
1189   return newbuf;
1190 }
1191 
1192 static void
gst_qt_mux_pad_add_ac3_extension(GstQTMux * qtmux,GstQTMuxPad * qtpad,guint8 fscod,guint8 frmsizcod,guint8 bsid,guint8 bsmod,guint8 acmod,guint8 lfe_on)1193 gst_qt_mux_pad_add_ac3_extension (GstQTMux * qtmux, GstQTMuxPad * qtpad,
1194     guint8 fscod, guint8 frmsizcod, guint8 bsid, guint8 bsmod, guint8 acmod,
1195     guint8 lfe_on)
1196 {
1197   AtomInfo *ext;
1198 
1199   g_return_if_fail (qtpad->trak_ste);
1200 
1201   ext = build_ac3_extension (fscod, bsid, bsmod, acmod, lfe_on, frmsizcod >> 1);        /* bitrate_code is inside frmsizcod */
1202 
1203   sample_table_entry_add_ext_atom (qtpad->trak_ste, ext);
1204 }
1205 
1206 static GstBuffer *
gst_qt_mux_prepare_parse_ac3_frame(GstQTMuxPad * qtpad,GstBuffer * buf,GstQTMux * qtmux)1207 gst_qt_mux_prepare_parse_ac3_frame (GstQTMuxPad * qtpad, GstBuffer * buf,
1208     GstQTMux * qtmux)
1209 {
1210   GstMapInfo map;
1211   GstByteReader reader;
1212   guint off;
1213 
1214   if (!gst_buffer_map (buf, &map, GST_MAP_READ)) {
1215     GST_WARNING_OBJECT (qtpad, "Failed to map buffer");
1216     return buf;
1217   }
1218 
1219   if (G_UNLIKELY (map.size < 8))
1220     goto done;
1221 
1222   gst_byte_reader_init (&reader, map.data, map.size);
1223   off = gst_byte_reader_masked_scan_uint32 (&reader, 0xffff0000, 0x0b770000,
1224       0, map.size);
1225 
1226   if (off != -1) {
1227     GstBitReader bits;
1228     guint8 fscod, frmsizcod, bsid, bsmod, acmod, lfe_on;
1229 
1230     GST_DEBUG_OBJECT (qtpad, "Found ac3 sync point at offset: %u", off);
1231 
1232     gst_bit_reader_init (&bits, map.data, map.size);
1233 
1234     /* off + sync + crc */
1235     gst_bit_reader_skip_unchecked (&bits, off * 8 + 16 + 16);
1236 
1237     fscod = gst_bit_reader_get_bits_uint8_unchecked (&bits, 2);
1238     frmsizcod = gst_bit_reader_get_bits_uint8_unchecked (&bits, 6);
1239     bsid = gst_bit_reader_get_bits_uint8_unchecked (&bits, 5);
1240     bsmod = gst_bit_reader_get_bits_uint8_unchecked (&bits, 3);
1241     acmod = gst_bit_reader_get_bits_uint8_unchecked (&bits, 3);
1242 
1243     if ((acmod & 0x1) && (acmod != 0x1))        /* 3 front channels */
1244       gst_bit_reader_skip_unchecked (&bits, 2);
1245     if ((acmod & 0x4))          /* if a surround channel exists */
1246       gst_bit_reader_skip_unchecked (&bits, 2);
1247     if (acmod == 0x2)           /* if in 2/0 mode */
1248       gst_bit_reader_skip_unchecked (&bits, 2);
1249 
1250     lfe_on = gst_bit_reader_get_bits_uint8_unchecked (&bits, 1);
1251 
1252     gst_qt_mux_pad_add_ac3_extension (qtmux, qtpad, fscod, frmsizcod, bsid,
1253         bsmod, acmod, lfe_on);
1254 
1255     /* AC-3 spec says that those values should be constant for the
1256      * whole stream when muxed in mp4. We trust the input follows it */
1257     GST_DEBUG_OBJECT (qtpad, "Data parsed, removing "
1258         "prepare buffer function");
1259     qtpad->prepare_buf_func = NULL;
1260   }
1261 
1262 done:
1263   gst_buffer_unmap (buf, &map);
1264   return buf;
1265 }
1266 
1267 static GstBuffer *
gst_qt_mux_create_empty_tx3g_buffer(GstQTMuxPad * qtpad,gint64 duration)1268 gst_qt_mux_create_empty_tx3g_buffer (GstQTMuxPad * qtpad, gint64 duration)
1269 {
1270   guint8 *data;
1271 
1272   data = g_malloc (2);
1273   GST_WRITE_UINT16_BE (data, 0);
1274 
1275   return gst_buffer_new_wrapped (data, 2);
1276 }
1277 
1278 static void
gst_qt_mux_add_mp4_tag(GstQTMux * qtmux,const GstTagList * list,AtomUDTA * udta,const char * tag,const char * tag2,guint32 fourcc)1279 gst_qt_mux_add_mp4_tag (GstQTMux * qtmux, const GstTagList * list,
1280     AtomUDTA * udta, const char *tag, const char *tag2, guint32 fourcc)
1281 {
1282   switch (gst_tag_get_type (tag)) {
1283       /* strings */
1284     case G_TYPE_STRING:
1285     {
1286       gchar *str = NULL;
1287 
1288       if (!gst_tag_list_get_string (list, tag, &str) || !str)
1289         break;
1290       GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
1291           GST_FOURCC_ARGS (fourcc), str);
1292       atom_udta_add_str_tag (udta, fourcc, str);
1293       g_free (str);
1294       break;
1295     }
1296       /* double */
1297     case G_TYPE_DOUBLE:
1298     {
1299       gdouble value;
1300 
1301       if (!gst_tag_list_get_double (list, tag, &value))
1302         break;
1303       GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u",
1304           GST_FOURCC_ARGS (fourcc), (gint) value);
1305       atom_udta_add_uint_tag (udta, fourcc, 21, (gint) value);
1306       break;
1307     }
1308     case G_TYPE_UINT:
1309     {
1310       guint value = 0;
1311       if (tag2) {
1312         /* paired unsigned integers */
1313         guint count = 0;
1314         gboolean got_tag;
1315 
1316         got_tag = gst_tag_list_get_uint (list, tag, &value);
1317         got_tag = gst_tag_list_get_uint (list, tag2, &count) || got_tag;
1318         if (!got_tag)
1319           break;
1320         GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u/%u",
1321             GST_FOURCC_ARGS (fourcc), value, count);
1322         atom_udta_add_uint_tag (udta, fourcc, 0,
1323             value << 16 | (count & 0xFFFF));
1324       } else {
1325         /* unpaired unsigned integers */
1326         if (!gst_tag_list_get_uint (list, tag, &value))
1327           break;
1328         GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u",
1329             GST_FOURCC_ARGS (fourcc), value);
1330         atom_udta_add_uint_tag (udta, fourcc, 1, value);
1331       }
1332       break;
1333     }
1334     default:
1335       g_assert_not_reached ();
1336       break;
1337   }
1338 }
1339 
1340 static void
gst_qt_mux_add_mp4_date(GstQTMux * qtmux,const GstTagList * list,AtomUDTA * udta,const char * tag,const char * tag2,guint32 fourcc)1341 gst_qt_mux_add_mp4_date (GstQTMux * qtmux, const GstTagList * list,
1342     AtomUDTA * udta, const char *tag, const char *tag2, guint32 fourcc)
1343 {
1344   GDate *date = NULL;
1345   GDateYear year;
1346   GDateMonth month;
1347   GDateDay day;
1348   gchar *str;
1349 
1350   g_return_if_fail (gst_tag_get_type (tag) == G_TYPE_DATE);
1351 
1352   if (!gst_tag_list_get_date (list, tag, &date) || !date)
1353     return;
1354 
1355   year = g_date_get_year (date);
1356   month = g_date_get_month (date);
1357   day = g_date_get_day (date);
1358 
1359   g_date_free (date);
1360 
1361   if (year == G_DATE_BAD_YEAR && month == G_DATE_BAD_MONTH &&
1362       day == G_DATE_BAD_DAY) {
1363     GST_WARNING_OBJECT (qtmux, "invalid date in tag");
1364     return;
1365   }
1366 
1367   str = g_strdup_printf ("%u-%u-%u", year, month, day);
1368   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
1369       GST_FOURCC_ARGS (fourcc), str);
1370   atom_udta_add_str_tag (udta, fourcc, str);
1371   g_free (str);
1372 }
1373 
1374 static void
gst_qt_mux_add_mp4_cover(GstQTMux * qtmux,const GstTagList * list,AtomUDTA * udta,const char * tag,const char * tag2,guint32 fourcc)1375 gst_qt_mux_add_mp4_cover (GstQTMux * qtmux, const GstTagList * list,
1376     AtomUDTA * udta, const char *tag, const char *tag2, guint32 fourcc)
1377 {
1378   GValue value = { 0, };
1379   GstBuffer *buf;
1380   GstSample *sample;
1381   GstCaps *caps;
1382   GstStructure *structure;
1383   gint flags = 0;
1384   GstMapInfo map;
1385 
1386   g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_SAMPLE);
1387 
1388   if (!gst_tag_list_copy_value (&value, list, tag))
1389     return;
1390 
1391   sample = gst_value_get_sample (&value);
1392 
1393   if (!sample)
1394     goto done;
1395 
1396   buf = gst_sample_get_buffer (sample);
1397   if (!buf)
1398     goto done;
1399 
1400   caps = gst_sample_get_caps (sample);
1401   if (!caps) {
1402     GST_WARNING_OBJECT (qtmux, "preview image without caps");
1403     goto done;
1404   }
1405 
1406   GST_DEBUG_OBJECT (qtmux, "preview image caps %" GST_PTR_FORMAT, caps);
1407 
1408   structure = gst_caps_get_structure (caps, 0);
1409   if (gst_structure_has_name (structure, "image/jpeg"))
1410     flags = 13;
1411   else if (gst_structure_has_name (structure, "image/png"))
1412     flags = 14;
1413 
1414   if (!flags) {
1415     GST_WARNING_OBJECT (qtmux, "preview image format not supported");
1416     goto done;
1417   }
1418 
1419   gst_buffer_map (buf, &map, GST_MAP_READ);
1420   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT
1421       " -> image size %" G_GSIZE_FORMAT "", GST_FOURCC_ARGS (fourcc), map.size);
1422   atom_udta_add_tag (udta, fourcc, flags, map.data, map.size);
1423   gst_buffer_unmap (buf, &map);
1424 done:
1425   g_value_unset (&value);
1426 }
1427 
1428 static void
gst_qt_mux_add_3gp_str(GstQTMux * qtmux,const GstTagList * list,AtomUDTA * udta,const char * tag,const char * tag2,guint32 fourcc)1429 gst_qt_mux_add_3gp_str (GstQTMux * qtmux, const GstTagList * list,
1430     AtomUDTA * udta, const char *tag, const char *tag2, guint32 fourcc)
1431 {
1432   gchar *str = NULL;
1433   guint number;
1434 
1435   g_return_if_fail (gst_tag_get_type (tag) == G_TYPE_STRING);
1436   g_return_if_fail (!tag2 || gst_tag_get_type (tag2) == G_TYPE_UINT);
1437 
1438   if (!gst_tag_list_get_string (list, tag, &str) || !str)
1439     return;
1440 
1441   if (tag2)
1442     if (!gst_tag_list_get_uint (list, tag2, &number))
1443       tag2 = NULL;
1444 
1445   if (!tag2) {
1446     GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
1447         GST_FOURCC_ARGS (fourcc), str);
1448     atom_udta_add_3gp_str_tag (udta, fourcc, str);
1449   } else {
1450     GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s/%d",
1451         GST_FOURCC_ARGS (fourcc), str, number);
1452     atom_udta_add_3gp_str_int_tag (udta, fourcc, str, number);
1453   }
1454 
1455   g_free (str);
1456 }
1457 
1458 static void
gst_qt_mux_add_3gp_date(GstQTMux * qtmux,const GstTagList * list,AtomUDTA * udta,const char * tag,const char * tag2,guint32 fourcc)1459 gst_qt_mux_add_3gp_date (GstQTMux * qtmux, const GstTagList * list,
1460     AtomUDTA * udta, const char *tag, const char *tag2, guint32 fourcc)
1461 {
1462   GDate *date = NULL;
1463   GDateYear year;
1464 
1465   g_return_if_fail (gst_tag_get_type (tag) == G_TYPE_DATE);
1466 
1467   if (!gst_tag_list_get_date (list, tag, &date) || !date)
1468     return;
1469 
1470   year = g_date_get_year (date);
1471   g_date_free (date);
1472 
1473   if (year == G_DATE_BAD_YEAR) {
1474     GST_WARNING_OBJECT (qtmux, "invalid date in tag");
1475     return;
1476   }
1477 
1478   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %d",
1479       GST_FOURCC_ARGS (fourcc), year);
1480   atom_udta_add_3gp_uint_tag (udta, fourcc, year);
1481 }
1482 
1483 static void
gst_qt_mux_add_3gp_location(GstQTMux * qtmux,const GstTagList * list,AtomUDTA * udta,const char * tag,const char * tag2,guint32 fourcc)1484 gst_qt_mux_add_3gp_location (GstQTMux * qtmux, const GstTagList * list,
1485     AtomUDTA * udta, const char *tag, const char *tag2, guint32 fourcc)
1486 {
1487   gdouble latitude = -360, longitude = -360, altitude = 0;
1488   gchar *location = NULL;
1489   guint8 *data, *ddata;
1490   gint size = 0, len = 0;
1491   gboolean ret = FALSE;
1492 
1493   g_return_if_fail (strcmp (tag, GST_TAG_GEO_LOCATION_NAME) == 0);
1494 
1495   ret = gst_tag_list_get_string (list, tag, &location);
1496   ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_LONGITUDE,
1497       &longitude);
1498   ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_LATITUDE,
1499       &latitude);
1500   ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_ELEVATION,
1501       &altitude);
1502 
1503   if (!ret)
1504     return;
1505 
1506   if (location)
1507     len = strlen (location);
1508   size += len + 1 + 2;
1509 
1510   /* role + (long, lat, alt) + body + notes */
1511   size += 1 + 3 * 4 + 1 + 1;
1512 
1513   data = ddata = g_malloc (size);
1514 
1515   /* language tag */
1516   GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE));
1517   /* location */
1518   if (location)
1519     memcpy (data + 2, location, len);
1520   GST_WRITE_UINT8 (data + 2 + len, 0);
1521   data += len + 1 + 2;
1522   /* role */
1523   GST_WRITE_UINT8 (data, 0);
1524   /* long, lat, alt */
1525 #define QT_WRITE_SFP32(data, fp) GST_WRITE_UINT32_BE(data, (guint32) ((gint) (fp * 65536.0)))
1526   QT_WRITE_SFP32 (data + 1, longitude);
1527   QT_WRITE_SFP32 (data + 5, latitude);
1528   QT_WRITE_SFP32 (data + 9, altitude);
1529   /* neither astronomical body nor notes */
1530   GST_WRITE_UINT16_BE (data + 13, 0);
1531 
1532   GST_DEBUG_OBJECT (qtmux, "Adding tag 'loci'");
1533   atom_udta_add_3gp_tag (udta, fourcc, ddata, size);
1534   g_free (ddata);
1535 }
1536 
1537 static void
gst_qt_mux_add_3gp_keywords(GstQTMux * qtmux,const GstTagList * list,AtomUDTA * udta,const char * tag,const char * tag2,guint32 fourcc)1538 gst_qt_mux_add_3gp_keywords (GstQTMux * qtmux, const GstTagList * list,
1539     AtomUDTA * udta, const char *tag, const char *tag2, guint32 fourcc)
1540 {
1541   gchar *keywords = NULL;
1542   guint8 *data, *ddata;
1543   gint size = 0, i;
1544   gchar **kwds;
1545 
1546   g_return_if_fail (strcmp (tag, GST_TAG_KEYWORDS) == 0);
1547 
1548   if (!gst_tag_list_get_string (list, tag, &keywords) || !keywords)
1549     return;
1550 
1551   kwds = g_strsplit (keywords, ",", 0);
1552   g_free (keywords);
1553 
1554   size = 0;
1555   for (i = 0; kwds[i]; i++) {
1556     /* size byte + null-terminator */
1557     size += strlen (kwds[i]) + 1 + 1;
1558   }
1559 
1560   /* language tag + count + keywords */
1561   size += 2 + 1;
1562 
1563   data = ddata = g_malloc (size);
1564 
1565   /* language tag */
1566   GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE));
1567   /* count */
1568   GST_WRITE_UINT8 (data + 2, i);
1569   data += 3;
1570   /* keywords */
1571   for (i = 0; kwds[i]; ++i) {
1572     gint len = strlen (kwds[i]);
1573 
1574     GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
1575         GST_FOURCC_ARGS (fourcc), kwds[i]);
1576     /* size */
1577     GST_WRITE_UINT8 (data, len + 1);
1578     memcpy (data + 1, kwds[i], len + 1);
1579     data += len + 2;
1580   }
1581 
1582   g_strfreev (kwds);
1583 
1584   atom_udta_add_3gp_tag (udta, fourcc, ddata, size);
1585   g_free (ddata);
1586 }
1587 
1588 static gboolean
gst_qt_mux_parse_classification_string(GstQTMux * qtmux,const gchar * input,guint32 * p_fourcc,guint16 * p_table,gchar ** p_content)1589 gst_qt_mux_parse_classification_string (GstQTMux * qtmux, const gchar * input,
1590     guint32 * p_fourcc, guint16 * p_table, gchar ** p_content)
1591 {
1592   guint32 fourcc;
1593   gint table;
1594   gint size;
1595   const gchar *data;
1596 
1597   data = input;
1598   size = strlen (input);
1599 
1600   if (size < 4 + 3 + 1 + 1 + 1) {
1601     /* at least the minimum xxxx://y/z */
1602     GST_WARNING_OBJECT (qtmux, "Classification tag input (%s) too short, "
1603         "ignoring", input);
1604     return FALSE;
1605   }
1606 
1607   /* read the fourcc */
1608   memcpy (&fourcc, data, 4);
1609   size -= 4;
1610   data += 4;
1611 
1612   if (strncmp (data, "://", 3) != 0) {
1613     goto mismatch;
1614   }
1615   data += 3;
1616   size -= 3;
1617 
1618   /* read the table number */
1619   if (sscanf (data, "%d", &table) != 1) {
1620     goto mismatch;
1621   }
1622   if (table < 0) {
1623     GST_WARNING_OBJECT (qtmux, "Invalid table number in classification tag (%d)"
1624         ", table numbers should be positive, ignoring tag", table);
1625     return FALSE;
1626   }
1627 
1628   /* find the next / */
1629   while (size > 0 && data[0] != '/') {
1630     data += 1;
1631     size -= 1;
1632   }
1633   if (size == 0) {
1634     goto mismatch;
1635   }
1636   g_assert (data[0] == '/');
1637 
1638   /* skip the '/' */
1639   data += 1;
1640   size -= 1;
1641   if (size == 0) {
1642     goto mismatch;
1643   }
1644 
1645   /* read up the rest of the string */
1646   *p_content = g_strdup (data);
1647   *p_table = (guint16) table;
1648   *p_fourcc = fourcc;
1649   return TRUE;
1650 
1651 mismatch:
1652   {
1653     GST_WARNING_OBJECT (qtmux, "Ignoring classification tag as "
1654         "input (%s) didn't match the expected entitycode://table/content",
1655         input);
1656     return FALSE;
1657   }
1658 }
1659 
1660 static void
gst_qt_mux_add_3gp_classification(GstQTMux * qtmux,const GstTagList * list,AtomUDTA * udta,const char * tag,const char * tag2,guint32 fourcc)1661 gst_qt_mux_add_3gp_classification (GstQTMux * qtmux, const GstTagList * list,
1662     AtomUDTA * udta, const char *tag, const char *tag2, guint32 fourcc)
1663 {
1664   gchar *clsf_data = NULL;
1665   gint size = 0;
1666   guint32 entity = 0;
1667   guint16 table = 0;
1668   gchar *content = NULL;
1669   guint8 *data;
1670 
1671   g_return_if_fail (strcmp (tag, GST_TAG_3GP_CLASSIFICATION) == 0);
1672 
1673   if (!gst_tag_list_get_string (list, tag, &clsf_data) || !clsf_data)
1674     return;
1675 
1676   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
1677       GST_FOURCC_ARGS (fourcc), clsf_data);
1678 
1679   /* parse the string, format is:
1680    * entityfourcc://table/content
1681    */
1682   gst_qt_mux_parse_classification_string (qtmux, clsf_data, &entity, &table,
1683       &content);
1684   g_free (clsf_data);
1685   /* +1 for the \0 */
1686   size = strlen (content) + 1;
1687 
1688   /* now we have everything, build the atom
1689    * atom description is at 3GPP TS 26.244 V8.2.0 (2009-09) */
1690   data = g_malloc (4 + 2 + 2 + size);
1691   GST_WRITE_UINT32_LE (data, entity);
1692   GST_WRITE_UINT16_BE (data + 4, (guint16) table);
1693   GST_WRITE_UINT16_BE (data + 6, 0);
1694   memcpy (data + 8, content, size);
1695   g_free (content);
1696 
1697   atom_udta_add_3gp_tag (udta, fourcc, data, 4 + 2 + 2 + size);
1698   g_free (data);
1699 }
1700 
1701 typedef void (*GstQTMuxAddUdtaTagFunc) (GstQTMux * mux,
1702     const GstTagList * list, AtomUDTA * udta, const char *tag,
1703     const char *tag2, guint32 fourcc);
1704 
1705 /*
1706  * Struct to record mappings from gstreamer tags to fourcc codes
1707  */
1708 typedef struct _GstTagToFourcc
1709 {
1710   guint32 fourcc;
1711   const gchar *gsttag;
1712   const gchar *gsttag2;
1713   const GstQTMuxAddUdtaTagFunc func;
1714 } GstTagToFourcc;
1715 
1716 /* tag list tags to fourcc matching */
1717 static const GstTagToFourcc tag_matches_mp4[] = {
1718   {FOURCC__alb, GST_TAG_ALBUM, NULL, gst_qt_mux_add_mp4_tag},
1719   {FOURCC_soal, GST_TAG_ALBUM_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
1720   {FOURCC__ART, GST_TAG_ARTIST, NULL, gst_qt_mux_add_mp4_tag},
1721   {FOURCC_soar, GST_TAG_ARTIST_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
1722   {FOURCC_aART, GST_TAG_ALBUM_ARTIST, NULL, gst_qt_mux_add_mp4_tag},
1723   {FOURCC_soaa, GST_TAG_ALBUM_ARTIST_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
1724   {FOURCC__swr, GST_TAG_APPLICATION_NAME, NULL, gst_qt_mux_add_mp4_tag},
1725   {FOURCC__cmt, GST_TAG_COMMENT, NULL, gst_qt_mux_add_mp4_tag},
1726   {FOURCC__wrt, GST_TAG_COMPOSER, NULL, gst_qt_mux_add_mp4_tag},
1727   {FOURCC_soco, GST_TAG_COMPOSER_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
1728   {FOURCC_tvsh, GST_TAG_SHOW_NAME, NULL, gst_qt_mux_add_mp4_tag},
1729   {FOURCC_sosn, GST_TAG_SHOW_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
1730   {FOURCC_tvsn, GST_TAG_SHOW_SEASON_NUMBER, NULL, gst_qt_mux_add_mp4_tag},
1731   {FOURCC_tves, GST_TAG_SHOW_EPISODE_NUMBER, NULL, gst_qt_mux_add_mp4_tag},
1732   {FOURCC__gen, GST_TAG_GENRE, NULL, gst_qt_mux_add_mp4_tag},
1733   {FOURCC__nam, GST_TAG_TITLE, NULL, gst_qt_mux_add_mp4_tag},
1734   {FOURCC_sonm, GST_TAG_TITLE_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
1735   {FOURCC_perf, GST_TAG_PERFORMER, NULL, gst_qt_mux_add_mp4_tag},
1736   {FOURCC__grp, GST_TAG_GROUPING, NULL, gst_qt_mux_add_mp4_tag},
1737   {FOURCC__des, GST_TAG_DESCRIPTION, NULL, gst_qt_mux_add_mp4_tag},
1738   {FOURCC__lyr, GST_TAG_LYRICS, NULL, gst_qt_mux_add_mp4_tag},
1739   {FOURCC__too, GST_TAG_ENCODER, NULL, gst_qt_mux_add_mp4_tag},
1740   {FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, gst_qt_mux_add_mp4_tag},
1741   {FOURCC_keyw, GST_TAG_KEYWORDS, NULL, gst_qt_mux_add_mp4_tag},
1742   {FOURCC__day, GST_TAG_DATE, NULL, gst_qt_mux_add_mp4_date},
1743   {FOURCC_tmpo, GST_TAG_BEATS_PER_MINUTE, NULL, gst_qt_mux_add_mp4_tag},
1744   {FOURCC_trkn, GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT,
1745       gst_qt_mux_add_mp4_tag},
1746   {FOURCC_disk, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT,
1747       gst_qt_mux_add_mp4_tag},
1748   {FOURCC_covr, GST_TAG_PREVIEW_IMAGE, NULL, gst_qt_mux_add_mp4_cover},
1749   {FOURCC_covr, GST_TAG_IMAGE, NULL, gst_qt_mux_add_mp4_cover},
1750   {0, NULL,}
1751 };
1752 
1753 static const GstTagToFourcc tag_matches_3gp[] = {
1754   {FOURCC_titl, GST_TAG_TITLE, NULL, gst_qt_mux_add_3gp_str},
1755   {FOURCC_dscp, GST_TAG_DESCRIPTION, NULL, gst_qt_mux_add_3gp_str},
1756   {FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, gst_qt_mux_add_3gp_str},
1757   {FOURCC_perf, GST_TAG_ARTIST, NULL, gst_qt_mux_add_3gp_str},
1758   {FOURCC_auth, GST_TAG_COMPOSER, NULL, gst_qt_mux_add_3gp_str},
1759   {FOURCC_gnre, GST_TAG_GENRE, NULL, gst_qt_mux_add_3gp_str},
1760   {FOURCC_kywd, GST_TAG_KEYWORDS, NULL, gst_qt_mux_add_3gp_keywords},
1761   {FOURCC_yrrc, GST_TAG_DATE, NULL, gst_qt_mux_add_3gp_date},
1762   {FOURCC_albm, GST_TAG_ALBUM, GST_TAG_TRACK_NUMBER, gst_qt_mux_add_3gp_str},
1763   {FOURCC_loci, GST_TAG_GEO_LOCATION_NAME, NULL, gst_qt_mux_add_3gp_location},
1764   {FOURCC_clsf, GST_TAG_3GP_CLASSIFICATION, NULL,
1765       gst_qt_mux_add_3gp_classification},
1766   {0, NULL,}
1767 };
1768 
1769 /* qtdemux produces these for atoms it cannot parse */
1770 #define GST_QT_DEMUX_PRIVATE_TAG "private-qt-tag"
1771 
1772 static void
gst_qt_mux_add_xmp_tags(GstQTMux * qtmux,const GstTagList * list)1773 gst_qt_mux_add_xmp_tags (GstQTMux * qtmux, const GstTagList * list)
1774 {
1775   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1776   GstBuffer *xmp = NULL;
1777 
1778   /* adobe specs only have 'quicktime' and 'mp4',
1779    * but I guess we can extrapolate to gpp.
1780    * Keep mj2 out for now as we don't add any tags for it yet.
1781    * If you have further info about xmp on these formats, please share */
1782   if (qtmux_klass->format == GST_QT_MUX_FORMAT_MJ2)
1783     return;
1784 
1785   GST_DEBUG_OBJECT (qtmux, "Adding xmp tags");
1786 
1787   if (qtmux_klass->format == GST_QT_MUX_FORMAT_QT) {
1788     xmp = gst_tag_xmp_writer_tag_list_to_xmp_buffer (GST_TAG_XMP_WRITER (qtmux),
1789         list, TRUE);
1790     if (xmp)
1791       atom_udta_add_xmp_tags (&qtmux->moov->udta, xmp);
1792   } else {
1793     AtomInfo *ainfo;
1794     /* for isom/mp4, it is a top level uuid atom */
1795     xmp = gst_tag_xmp_writer_tag_list_to_xmp_buffer (GST_TAG_XMP_WRITER (qtmux),
1796         list, TRUE);
1797     if (xmp) {
1798       ainfo = build_uuid_xmp_atom (xmp);
1799       if (ainfo) {
1800         qtmux->extra_atoms = g_slist_prepend (qtmux->extra_atoms, ainfo);
1801       }
1802     }
1803   }
1804   if (xmp)
1805     gst_buffer_unref (xmp);
1806 }
1807 
1808 static void
gst_qt_mux_add_metadata_tags(GstQTMux * qtmux,const GstTagList * list,AtomUDTA * udta)1809 gst_qt_mux_add_metadata_tags (GstQTMux * qtmux, const GstTagList * list,
1810     AtomUDTA * udta)
1811 {
1812   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1813   guint32 fourcc;
1814   gint i;
1815   const gchar *tag, *tag2;
1816   const GstTagToFourcc *tag_matches;
1817 
1818   switch (qtmux_klass->format) {
1819     case GST_QT_MUX_FORMAT_3GP:
1820       tag_matches = tag_matches_3gp;
1821       break;
1822     case GST_QT_MUX_FORMAT_MJ2:
1823       tag_matches = NULL;
1824       break;
1825     default:
1826       /* sort of iTunes style for mp4 and QT (?) */
1827       tag_matches = tag_matches_mp4;
1828       break;
1829   }
1830 
1831   if (!tag_matches)
1832     return;
1833 
1834   /* Clear existing tags so we don't add them over and over */
1835   atom_udta_clear_tags (udta);
1836 
1837   for (i = 0; tag_matches[i].fourcc; i++) {
1838     fourcc = tag_matches[i].fourcc;
1839     tag = tag_matches[i].gsttag;
1840     tag2 = tag_matches[i].gsttag2;
1841 
1842     g_assert (tag_matches[i].func);
1843     tag_matches[i].func (qtmux, list, udta, tag, tag2, fourcc);
1844   }
1845 
1846   /* add unparsed blobs if present */
1847   if (gst_tag_exists (GST_QT_DEMUX_PRIVATE_TAG)) {
1848     guint num_tags;
1849 
1850     num_tags = gst_tag_list_get_tag_size (list, GST_QT_DEMUX_PRIVATE_TAG);
1851     for (i = 0; i < num_tags; ++i) {
1852       GstSample *sample = NULL;
1853       GstBuffer *buf;
1854       const GstStructure *s;
1855 
1856       if (!gst_tag_list_get_sample_index (list, GST_QT_DEMUX_PRIVATE_TAG, i,
1857               &sample))
1858         continue;
1859       buf = gst_sample_get_buffer (sample);
1860 
1861       if (buf && (s = gst_sample_get_info (sample))) {
1862         const gchar *style = NULL;
1863         GstMapInfo map;
1864 
1865         gst_buffer_map (buf, &map, GST_MAP_READ);
1866         GST_DEBUG_OBJECT (qtmux,
1867             "Found private tag %d/%d; size %" G_GSIZE_FORMAT ", info %"
1868             GST_PTR_FORMAT, i, num_tags, map.size, s);
1869         if (s && (style = gst_structure_get_string (s, "style"))) {
1870           /* try to prevent some style tag ending up into another variant
1871            * (todo: make into a list if more cases) */
1872           if ((strcmp (style, "itunes") == 0 &&
1873                   qtmux_klass->format == GST_QT_MUX_FORMAT_MP4) ||
1874               (strcmp (style, "iso") == 0 &&
1875                   qtmux_klass->format == GST_QT_MUX_FORMAT_3GP)) {
1876             GST_DEBUG_OBJECT (qtmux, "Adding private tag");
1877             atom_udta_add_blob_tag (udta, map.data, map.size);
1878           }
1879         }
1880         gst_buffer_unmap (buf, &map);
1881       }
1882       gst_sample_unref (sample);
1883     }
1884   }
1885 
1886   return;
1887 }
1888 
1889 /*
1890  * Gets the tagsetter iface taglist and puts the known tags
1891  * into the output stream
1892  */
1893 static void
gst_qt_mux_setup_metadata(GstQTMux * qtmux)1894 gst_qt_mux_setup_metadata (GstQTMux * qtmux)
1895 {
1896   const GstTagList *tags = NULL;
1897   GList *l;
1898 
1899   GST_OBJECT_LOCK (qtmux);
1900   if (qtmux->tags_changed) {
1901     tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (qtmux));
1902     qtmux->tags_changed = FALSE;
1903   }
1904   GST_OBJECT_UNLOCK (qtmux);
1905 
1906   GST_LOG_OBJECT (qtmux, "tags: %" GST_PTR_FORMAT, tags);
1907 
1908   if (tags && !gst_tag_list_is_empty (tags)) {
1909     GstTagList *copy = gst_tag_list_copy (tags);
1910 
1911     GST_DEBUG_OBJECT (qtmux, "Removing bogus tags");
1912     gst_tag_list_remove_tag (copy, GST_TAG_VIDEO_CODEC);
1913     gst_tag_list_remove_tag (copy, GST_TAG_AUDIO_CODEC);
1914     gst_tag_list_remove_tag (copy, GST_TAG_CONTAINER_FORMAT);
1915 
1916     GST_DEBUG_OBJECT (qtmux, "Formatting tags");
1917     gst_qt_mux_add_metadata_tags (qtmux, copy, &qtmux->moov->udta);
1918     gst_qt_mux_add_xmp_tags (qtmux, copy);
1919     gst_tag_list_unref (copy);
1920   } else {
1921     GST_DEBUG_OBJECT (qtmux, "No new tags received");
1922   }
1923 
1924   GST_OBJECT_LOCK (qtmux);
1925   for (l = GST_ELEMENT (qtmux)->sinkpads; l; l = l->next) {
1926     GstQTMuxPad *qpad = GST_QT_MUX_PAD (l->data);
1927 
1928     if (qpad->tags_changed && qpad->tags) {
1929       GST_DEBUG_OBJECT (qpad, "Adding tags");
1930       gst_tag_list_remove_tag (qpad->tags, GST_TAG_CONTAINER_FORMAT);
1931       gst_qt_mux_add_metadata_tags (qtmux, qpad->tags, &qpad->trak->udta);
1932       qpad->tags_changed = FALSE;
1933       GST_DEBUG_OBJECT (qpad, "Tags added");
1934     } else {
1935       GST_DEBUG_OBJECT (qpad, "No new tags received");
1936     }
1937   }
1938   GST_OBJECT_UNLOCK (qtmux);
1939 }
1940 
1941 static inline GstBuffer *
_gst_buffer_new_take_data(guint8 * data,guint size)1942 _gst_buffer_new_take_data (guint8 * data, guint size)
1943 {
1944   GstBuffer *buf;
1945 
1946   buf = gst_buffer_new ();
1947   gst_buffer_append_memory (buf,
1948       gst_memory_new_wrapped (0, data, size, 0, size, data, g_free));
1949 
1950   return buf;
1951 }
1952 
1953 static GstFlowReturn
gst_qt_mux_send_buffer(GstQTMux * qtmux,GstBuffer * buf,guint64 * offset,gboolean mind_fast)1954 gst_qt_mux_send_buffer (GstQTMux * qtmux, GstBuffer * buf, guint64 * offset,
1955     gboolean mind_fast)
1956 {
1957   GstFlowReturn res = GST_FLOW_OK;
1958   gsize size;
1959 
1960   g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR);
1961 
1962   size = gst_buffer_get_size (buf);
1963   GST_LOG_OBJECT (qtmux, "sending buffer size %" G_GSIZE_FORMAT, size);
1964 
1965 /* ohos.opt.compat.0011
1966  * qtmux itself does not handle flush events, so in extreme cases, the buffer is discarded by gstpad
1967  * when it is passed forward, but qtmux thinks that the buffer writes the file successfully,
1968  * resulting in a file exception.
1969  */
1970 #ifdef OHOS_OPT_COMPAT
1971   g_mutex_lock(&qtmux->flush_lock);
1972   if (qtmux->is_flushing) {
1973       g_mutex_unlock(&qtmux->flush_lock);
1974       GST_INFO_OBJECT(qtmux, "drop buffer size %" G_GSIZE_FORMAT, size);
1975       return GST_FLOW_FLUSHING;
1976   }
1977 #endif
1978 
1979   if (mind_fast && qtmux->fast_start_file) {
1980     GstMapInfo map;
1981     gint ret;
1982 
1983     GST_LOG_OBJECT (qtmux, "to temporary file");
1984     gst_buffer_map (buf, &map, GST_MAP_READ);
1985     ret = fwrite (map.data, sizeof (guint8), map.size, qtmux->fast_start_file);
1986     gst_buffer_unmap (buf, &map);
1987     gst_buffer_unref (buf);
1988     if (ret != size)
1989       goto write_error;
1990     else
1991       res = GST_FLOW_OK;
1992   } else {
1993     if (!mind_fast) {
1994       res = gst_qtmux_push_mdat_stored_buffers (qtmux);
1995     }
1996 
1997     if (res == GST_FLOW_OK) {
1998       GST_LOG_OBJECT (qtmux, "downstream");
1999       res = gst_aggregator_finish_buffer (GST_AGGREGATOR (qtmux), buf);
2000     }
2001   }
2002 
2003   if (res != GST_FLOW_OK)
2004     GST_WARNING_OBJECT (qtmux,
2005         "Failed to send buffer (%p) size %" G_GSIZE_FORMAT, buf, size);
2006 
2007   if (G_LIKELY (offset))
2008     *offset += size;
2009 
2010 /* ohos.opt.compat.0011
2011  * qtmux itself does not handle flush events, so in extreme cases, the buffer is discarded by gstpad
2012  * when it is passed forward, but qtmux thinks that the buffer writes the file successfully,
2013  * resulting in a file exception.
2014  */
2015 #ifdef OHOS_OPT_COMPAT
2016   g_mutex_unlock(&qtmux->flush_lock);
2017 #endif
2018 
2019   return res;
2020 
2021   /* ERRORS */
2022 write_error:
2023   {
2024     GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
2025         ("Failed to write to temporary file"), GST_ERROR_SYSTEM);
2026     return GST_FLOW_ERROR;
2027   }
2028 }
2029 
2030 static gboolean
gst_qt_mux_seek_to_beginning(FILE * f)2031 gst_qt_mux_seek_to_beginning (FILE * f)
2032 {
2033 #ifdef HAVE_FSEEKO
2034   if (fseeko (f, (off_t) 0, SEEK_SET) != 0)
2035     return FALSE;
2036 #elif defined (G_OS_UNIX) || defined (G_OS_WIN32)
2037   if (lseek (fileno (f), (off_t) 0, SEEK_SET) == (off_t) - 1)
2038     return FALSE;
2039 #else
2040   if (fseek (f, (long) 0, SEEK_SET) != 0)
2041     return FALSE;
2042 #endif
2043   return TRUE;
2044 }
2045 
2046 static GstFlowReturn
gst_qt_mux_send_buffered_data(GstQTMux * qtmux,guint64 * offset)2047 gst_qt_mux_send_buffered_data (GstQTMux * qtmux, guint64 * offset)
2048 {
2049   GstFlowReturn ret = GST_FLOW_OK;
2050   GstBuffer *buf = NULL;
2051 
2052   if (fflush (qtmux->fast_start_file))
2053     goto flush_failed;
2054 
2055   if (!gst_qt_mux_seek_to_beginning (qtmux->fast_start_file))
2056     goto seek_failed;
2057 
2058   /* hm, this could all take a really really long time,
2059    * but there may not be another way to get moov atom first
2060    * (somehow optimize copy?) */
2061   GST_DEBUG_OBJECT (qtmux, "Sending buffered data");
2062   while (ret == GST_FLOW_OK) {
2063     const int bufsize = 4096;
2064     GstMapInfo map;
2065     gsize size;
2066 
2067     buf = gst_buffer_new_and_alloc (bufsize);
2068     gst_buffer_map (buf, &map, GST_MAP_WRITE);
2069     size = fread (map.data, sizeof (guint8), bufsize, qtmux->fast_start_file);
2070     if (size == 0) {
2071       gst_buffer_unmap (buf, &map);
2072       break;
2073     }
2074     GST_LOG_OBJECT (qtmux, "Pushing buffered buffer of size %d", (gint) size);
2075     gst_buffer_unmap (buf, &map);
2076     if (size != bufsize)
2077       gst_buffer_set_size (buf, size);
2078     ret = gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
2079     buf = NULL;
2080   }
2081   if (buf)
2082     gst_buffer_unref (buf);
2083 
2084   if (ftruncate (fileno (qtmux->fast_start_file), 0))
2085     goto seek_failed;
2086   if (!gst_qt_mux_seek_to_beginning (qtmux->fast_start_file))
2087     goto seek_failed;
2088 
2089   return ret;
2090 
2091   /* ERRORS */
2092 flush_failed:
2093   {
2094     GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
2095         ("Failed to flush temporary file"), GST_ERROR_SYSTEM);
2096     ret = GST_FLOW_ERROR;
2097     goto fail;
2098   }
2099 seek_failed:
2100   {
2101     GST_ELEMENT_ERROR (qtmux, RESOURCE, SEEK,
2102         ("Failed to seek temporary file"), GST_ERROR_SYSTEM);
2103     ret = GST_FLOW_ERROR;
2104     goto fail;
2105   }
2106 fail:
2107   {
2108     /* clear descriptor so we don't remove temp file later on,
2109      * might be possible to recover */
2110     fclose (qtmux->fast_start_file);
2111     qtmux->fast_start_file = NULL;
2112     return ret;
2113   }
2114 }
2115 
2116 /*
2117  * Sends the initial mdat atom fields (size fields and fourcc type),
2118  * the subsequent buffers are considered part of it's data.
2119  * As we can't predict the amount of data that we are going to place in mdat
2120  * we need to record the position of the size field in the stream so we can
2121  * seek back to it later and update when the streams have finished.
2122  */
2123 static GstFlowReturn
gst_qt_mux_send_mdat_header(GstQTMux * qtmux,guint64 * off,guint64 size,gboolean extended,gboolean fsync_after)2124 gst_qt_mux_send_mdat_header (GstQTMux * qtmux, guint64 * off, guint64 size,
2125     gboolean extended, gboolean fsync_after)
2126 {
2127   GstBuffer *buf;
2128   GstMapInfo map;
2129   gboolean mind_fast = FALSE;
2130 
2131   GST_DEBUG_OBJECT (qtmux, "Sending mdat's atom header, "
2132       "size %" G_GUINT64_FORMAT, size);
2133 
2134   /* if the qtmux state is EOS, really write the mdat, otherwise
2135    * allow size == 0 for a placeholder atom */
2136   if (qtmux->state == GST_QT_MUX_STATE_EOS || size > 0)
2137     size += 8;
2138 
2139   if (extended) {
2140     gboolean large_file = (size > MDAT_LARGE_FILE_LIMIT);
2141     /* Always write 16-bytes, but put a free atom first
2142      * if the size is < 4GB. */
2143     buf = gst_buffer_new_and_alloc (16);
2144     gst_buffer_map (buf, &map, GST_MAP_WRITE);
2145 
2146     if (large_file) {
2147       /* Write extended mdat header and large_size field */
2148       GST_WRITE_UINT32_BE (map.data, 1);
2149       GST_WRITE_UINT32_LE (map.data + 4, FOURCC_mdat);
2150       GST_WRITE_UINT64_BE (map.data + 8, size + 8);
2151     } else {
2152       /* Write an empty free atom, then standard 32-bit mdat */
2153       GST_WRITE_UINT32_BE (map.data, 8);
2154       GST_WRITE_UINT32_LE (map.data + 4, FOURCC_free);
2155       GST_WRITE_UINT32_BE (map.data + 8, size);
2156       GST_WRITE_UINT32_LE (map.data + 12, FOURCC_mdat);
2157     }
2158     gst_buffer_unmap (buf, &map);
2159   } else {
2160     buf = gst_buffer_new_and_alloc (8);
2161     gst_buffer_map (buf, &map, GST_MAP_WRITE);
2162 
2163     /* Vanilla 32-bit mdat */
2164     GST_WRITE_UINT32_BE (map.data, size);
2165     GST_WRITE_UINT32_LE (map.data + 4, FOURCC_mdat);
2166     gst_buffer_unmap (buf, &map);
2167   }
2168 
2169   GST_LOG_OBJECT (qtmux, "Pushing mdat header");
2170   if (fsync_after)
2171     GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_SYNC_AFTER);
2172 
2173   mind_fast = qtmux->mux_mode == GST_QT_MUX_MODE_MOOV_AT_END
2174       && !qtmux->downstream_seekable;
2175 
2176   return gst_qt_mux_send_buffer (qtmux, buf, off, mind_fast);
2177 }
2178 
2179 static void
gst_qt_mux_seek_to(GstQTMux * qtmux,guint64 position)2180 gst_qt_mux_seek_to (GstQTMux * qtmux, guint64 position)
2181 {
2182   GstSegment segment;
2183 
2184   gst_segment_init (&segment, GST_FORMAT_BYTES);
2185   segment.start = position;
2186   GST_LOG_OBJECT (qtmux, "seeking to byte position %" G_GUINT64_FORMAT,
2187       position);
2188   gst_aggregator_update_segment (GST_AGGREGATOR (qtmux), &segment);
2189 }
2190 
2191 /*
2192  * We get the position of the mdat size field, seek back to it
2193  * and overwrite with the real value
2194  */
2195 static GstFlowReturn
gst_qt_mux_update_mdat_size(GstQTMux * qtmux,guint64 mdat_pos,guint64 mdat_size,guint64 * offset,gboolean fsync_after)2196 gst_qt_mux_update_mdat_size (GstQTMux * qtmux, guint64 mdat_pos,
2197     guint64 mdat_size, guint64 * offset, gboolean fsync_after)
2198 {
2199 
2200   /* We must have recorded the mdat position for this to work */
2201   g_assert (mdat_pos != 0);
2202 
2203   /* seek and rewrite the header */
2204   gst_qt_mux_seek_to (qtmux, mdat_pos);
2205 
2206   return gst_qt_mux_send_mdat_header (qtmux, offset, mdat_size, TRUE,
2207       fsync_after);
2208 }
2209 
2210 static GstFlowReturn
gst_qt_mux_send_ftyp(GstQTMux * qtmux,guint64 * off)2211 gst_qt_mux_send_ftyp (GstQTMux * qtmux, guint64 * off)
2212 {
2213   GstBuffer *buf;
2214   guint64 size = 0, offset = 0;
2215   guint8 *data = NULL;
2216 
2217   GST_DEBUG_OBJECT (qtmux, "Sending ftyp atom");
2218 
2219   if (!atom_ftyp_copy_data (qtmux->ftyp, &data, &size, &offset))
2220     goto serialize_error;
2221 
2222   buf = _gst_buffer_new_take_data (data, offset);
2223 
2224   GST_LOG_OBJECT (qtmux, "Pushing ftyp");
2225   return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
2226 
2227   /* ERRORS */
2228 serialize_error:
2229   {
2230     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
2231         ("Failed to serialize ftyp"));
2232     return GST_FLOW_ERROR;
2233   }
2234 }
2235 
2236 static void
gst_qt_mux_prepare_ftyp(GstQTMux * qtmux,AtomFTYP ** p_ftyp,GstBuffer ** p_prefix)2237 gst_qt_mux_prepare_ftyp (GstQTMux * qtmux, AtomFTYP ** p_ftyp,
2238     GstBuffer ** p_prefix)
2239 {
2240   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
2241   guint32 major, version;
2242   GList *comp;
2243   GstBuffer *prefix = NULL;
2244   AtomFTYP *ftyp = NULL;
2245 
2246   GST_DEBUG_OBJECT (qtmux, "Preparing ftyp and possible prefix atom");
2247 
2248   /* init and send context and ftyp based on current property state */
2249   gst_qt_mux_map_format_to_header (qtmux_klass->format, &prefix, &major,
2250       &version, &comp, qtmux->moov, qtmux->longest_chunk,
2251       qtmux->fast_start_file != NULL);
2252   ftyp = atom_ftyp_new (qtmux->context, major, version, comp);
2253   if (comp)
2254     g_list_free (comp);
2255   if (prefix) {
2256     if (p_prefix)
2257       *p_prefix = prefix;
2258     else
2259       gst_buffer_unref (prefix);
2260   }
2261   *p_ftyp = ftyp;
2262 }
2263 
2264 static GstFlowReturn
gst_qt_mux_prepare_and_send_ftyp(GstQTMux * qtmux)2265 gst_qt_mux_prepare_and_send_ftyp (GstQTMux * qtmux)
2266 {
2267   GstFlowReturn ret = GST_FLOW_OK;
2268   GstBuffer *prefix = NULL;
2269 
2270   GST_DEBUG_OBJECT (qtmux, "Preparing to send ftyp atom");
2271 
2272   /* init and send context and ftyp based on current property state */
2273   if (qtmux->ftyp) {
2274     atom_ftyp_free (qtmux->ftyp);
2275     qtmux->ftyp = NULL;
2276   }
2277   gst_qt_mux_prepare_ftyp (qtmux, &qtmux->ftyp, &prefix);
2278   if (prefix) {
2279     ret = gst_qt_mux_send_buffer (qtmux, prefix, &qtmux->header_size, FALSE);
2280     if (ret != GST_FLOW_OK)
2281       return ret;
2282   }
2283   return gst_qt_mux_send_ftyp (qtmux, &qtmux->header_size);
2284 }
2285 
2286 static void
gst_qt_mux_set_header_on_caps(GstQTMux * mux,GstBuffer * buf)2287 gst_qt_mux_set_header_on_caps (GstQTMux * mux, GstBuffer * buf)
2288 {
2289   GstStructure *structure;
2290   GValue array = { 0 };
2291   GValue value = { 0 };
2292   GstCaps *caps, *tcaps;
2293 
2294   tcaps = gst_pad_get_current_caps (GST_AGGREGATOR_SRC_PAD (mux));
2295   caps = gst_caps_copy (tcaps);
2296   gst_caps_unref (tcaps);
2297 
2298   structure = gst_caps_get_structure (caps, 0);
2299 
2300   g_value_init (&array, GST_TYPE_ARRAY);
2301 
2302   GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER);
2303   g_value_init (&value, GST_TYPE_BUFFER);
2304   gst_value_take_buffer (&value, gst_buffer_ref (buf));
2305   gst_value_array_append_value (&array, &value);
2306   g_value_unset (&value);
2307 
2308   gst_structure_set_value (structure, "streamheader", &array);
2309   g_value_unset (&array);
2310   gst_aggregator_set_src_caps (GST_AGGREGATOR (mux), caps);
2311   gst_caps_unref (caps);
2312 }
2313 
2314 /*
2315  * Write out a free space atom. The offset is adjusted by the full
2316  * size, but a smaller buffer is sent
2317  */
2318 static GstFlowReturn
gst_qt_mux_send_free_atom(GstQTMux * qtmux,guint64 * off,guint32 size,gboolean fsync_after)2319 gst_qt_mux_send_free_atom (GstQTMux * qtmux, guint64 * off, guint32 size,
2320     gboolean fsync_after)
2321 {
2322   Atom *node_header;
2323   GstBuffer *buf;
2324   guint8 *data = NULL;
2325   guint64 offset = 0, bsize = 0;
2326   GstFlowReturn ret;
2327 
2328   GST_DEBUG_OBJECT (qtmux, "Sending free atom header of size %u", size);
2329 
2330   /* We can't make a free space atom smaller than the header */
2331   if (size < 8)
2332     goto too_small;
2333 
2334   node_header = g_malloc0 (sizeof (Atom));
2335   node_header->type = FOURCC_free;
2336   node_header->size = size;
2337 
2338   bsize = offset = 0;
2339   if (atom_copy_data (node_header, &data, &bsize, &offset) == 0)
2340     goto serialize_error;
2341 
2342   buf = _gst_buffer_new_take_data (data, offset);
2343   g_free (node_header);
2344 
2345   if (fsync_after)
2346     GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_SYNC_AFTER);
2347 
2348   GST_LOG_OBJECT (qtmux, "Pushing free atom");
2349   ret = gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
2350 
2351   if (off) {
2352     *off += size - 8;
2353 
2354     /* Make sure downstream position ends up at the end of this free box */
2355     gst_qt_mux_seek_to (qtmux, *off);
2356   }
2357 
2358   return ret;
2359 
2360   /* ERRORS */
2361 too_small:
2362   {
2363     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
2364         ("Not enough free reserved space"));
2365     return GST_FLOW_ERROR;
2366   }
2367 serialize_error:
2368   {
2369     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
2370         ("Failed to serialize mdat"));
2371     g_free (node_header);
2372     return GST_FLOW_ERROR;
2373   }
2374 }
2375 
2376 static void
gst_qt_mux_configure_moov_full(GstQTMux * qtmux,gboolean fragmented,guint32 timescale)2377 gst_qt_mux_configure_moov_full (GstQTMux * qtmux, gboolean fragmented,
2378     guint32 timescale)
2379 {
2380   /* inform lower layers of our property wishes, and determine duration.
2381    * Let moov take care of this using its list of traks;
2382    * so that released pads are also included */
2383   GST_DEBUG_OBJECT (qtmux, "Updating timescale to %" G_GUINT32_FORMAT,
2384       timescale);
2385   atom_moov_update_timescale (qtmux->moov, timescale);
2386   atom_moov_set_fragmented (qtmux->moov, fragmented);
2387 
2388   atom_moov_update_duration (qtmux->moov);
2389 }
2390 
2391 static void
gst_qt_mux_configure_moov(GstQTMux * qtmux)2392 gst_qt_mux_configure_moov (GstQTMux * qtmux)
2393 {
2394   gboolean fragmented = FALSE;
2395   guint32 timescale;
2396 
2397   GST_OBJECT_LOCK (qtmux);
2398   timescale = qtmux->timescale;
2399   if (qtmux->mux_mode == GST_QT_MUX_MODE_FRAGMENTED
2400       && qtmux->fragment_mode != GST_QT_MUX_FRAGMENT_FIRST_MOOV_THEN_FINALISE)
2401     fragmented = TRUE;
2402   GST_OBJECT_UNLOCK (qtmux);
2403 
2404   gst_qt_mux_configure_moov_full (qtmux, fragmented, timescale);
2405 }
2406 
2407 static GstFlowReturn
gst_qt_mux_send_moov(GstQTMux * qtmux,guint64 * _offset,guint64 padded_moov_size,gboolean mind_fast,gboolean fsync_after)2408 gst_qt_mux_send_moov (GstQTMux * qtmux, guint64 * _offset,
2409     guint64 padded_moov_size, gboolean mind_fast, gboolean fsync_after)
2410 {
2411   guint64 offset = 0, size = 0;
2412   guint8 *data;
2413   GstBuffer *buf;
2414   GstFlowReturn ret = GST_FLOW_OK;
2415   GList *l;
2416   guint64 current_time = atoms_get_current_qt_time ();
2417 
2418   /* update modification times */
2419   qtmux->moov->mvhd.time_info.modification_time = current_time;
2420 
2421   GST_OBJECT_LOCK (qtmux);
2422   for (l = GST_ELEMENT_CAST (qtmux)->sinkpads; l; l = l->next) {
2423     GstQTMuxPad *qtpad = (GstQTMuxPad *) l->data;
2424 
2425     qtpad->trak->mdia.mdhd.time_info.modification_time = current_time;
2426     qtpad->trak->tkhd.modification_time = current_time;
2427 
2428 /* ohos.ext.func.0018
2429  * add additional features to set orientationHint in mp4 file.
2430  * { 0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000 }
2431  */
2432 #ifdef OHOS_EXT_FUNC
2433     switch (qtmux->rotation) {
2434     case 90:
2435         qtpad->trak->tkhd.matrix[0] = 0;
2436         qtpad->trak->tkhd.matrix[1] = 1 << 16;
2437         qtpad->trak->tkhd.matrix[3] = 65535 << 16;
2438         qtpad->trak->tkhd.matrix[4] = 0;
2439         break;
2440     case 180:
2441         qtpad->trak->tkhd.matrix[0] = 65535 << 16;
2442         qtpad->trak->tkhd.matrix[4] = 65535 << 16;
2443         break;
2444     case 270:
2445         qtpad->trak->tkhd.matrix[0] = 0;
2446         qtpad->trak->tkhd.matrix[1] = 65535 << 16;
2447         qtpad->trak->tkhd.matrix[3] = 1 << 16;
2448         qtpad->trak->tkhd.matrix[4] = 0;
2449         break;
2450     default:
2451         break;
2452     }
2453 #endif
2454   }
2455 
2456 /* ohos.ext.func.0016
2457  * add additional features to set geographic location information in mp4 file
2458  * the feature is default disable.
2459  */
2460 #ifdef OHOS_EXT_FUNC
2461   if (qtmux->enable_geolocation) {
2462       qtmux->moov->udta.set_location = TRUE;
2463       qtmux->moov->udta.latitude = qtmux->latitudex10000;
2464       qtmux->moov->udta.longitude = qtmux->longitudex10000;
2465   }
2466 #endif
2467   GST_OBJECT_UNLOCK (qtmux);
2468 
2469   /* serialize moov */
2470   offset = size = 0;
2471   data = NULL;
2472   GST_LOG_OBJECT (qtmux, "Copying movie header into buffer");
2473   if (!atom_moov_copy_data (qtmux->moov, &data, &size, &offset))
2474     goto serialize_error;
2475   qtmux->last_moov_size = offset;
2476 
2477   /* Check we have enough reserved space for this and a Free atom */
2478   if (padded_moov_size > 0 && offset + 8 > padded_moov_size)
2479     goto too_small_reserved;
2480   buf = _gst_buffer_new_take_data (data, offset);
2481   GST_DEBUG_OBJECT (qtmux, "Pushing moov atoms");
2482 
2483   /* If at EOS, this is the final moov, put in the streamheader
2484    * (apparently used by a flumotion util) */
2485   if (qtmux->state == GST_QT_MUX_STATE_EOS)
2486     gst_qt_mux_set_header_on_caps (qtmux, buf);
2487 
2488   if (fsync_after)
2489     GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_SYNC_AFTER);
2490   ret = gst_qt_mux_send_buffer (qtmux, buf, _offset, mind_fast);
2491 
2492   /* Write out a free atom if needed */
2493   if (ret == GST_FLOW_OK && offset < padded_moov_size) {
2494     GST_LOG_OBJECT (qtmux, "Writing out free atom of size %u",
2495         (guint32) (padded_moov_size - offset));
2496     ret =
2497         gst_qt_mux_send_free_atom (qtmux, _offset, padded_moov_size - offset,
2498         fsync_after);
2499   }
2500 
2501   return ret;
2502 too_small_reserved:
2503   {
2504     GST_ELEMENT_ERROR (qtmux, STREAM, MUX,
2505         ("Not enough free reserved header space"),
2506         ("Needed %" G_GUINT64_FORMAT " bytes, reserved %" G_GUINT64_FORMAT,
2507             offset + 8, padded_moov_size));
2508     return GST_FLOW_ERROR;
2509   }
2510 serialize_error:
2511   {
2512     g_free (data);
2513     return GST_FLOW_ERROR;
2514   }
2515 }
2516 
2517 /* either calculates size of extra atoms or pushes them */
2518 static GstFlowReturn
gst_qt_mux_send_extra_atoms(GstQTMux * qtmux,gboolean send,guint64 * offset,gboolean mind_fast)2519 gst_qt_mux_send_extra_atoms (GstQTMux * qtmux, gboolean send, guint64 * offset,
2520     gboolean mind_fast)
2521 {
2522   GSList *walk;
2523   guint64 loffset = 0, size = 0;
2524   guint8 *data;
2525   GstFlowReturn ret = GST_FLOW_OK;
2526 
2527   for (walk = qtmux->extra_atoms; walk; walk = g_slist_next (walk)) {
2528     AtomInfo *ainfo = (AtomInfo *) walk->data;
2529 
2530     loffset = size = 0;
2531     data = NULL;
2532     if (!ainfo->copy_data_func (ainfo->atom,
2533             send ? &data : NULL, &size, &loffset))
2534       goto serialize_error;
2535 
2536     if (send) {
2537       GstBuffer *buf;
2538 
2539       GST_DEBUG_OBJECT (qtmux,
2540           "Pushing extra top-level atom %" GST_FOURCC_FORMAT,
2541           GST_FOURCC_ARGS (ainfo->atom->type));
2542       buf = _gst_buffer_new_take_data (data, loffset);
2543       ret = gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
2544       if (ret != GST_FLOW_OK)
2545         break;
2546     } else {
2547       if (offset)
2548         *offset += loffset;
2549     }
2550   }
2551 
2552   return ret;
2553 
2554 serialize_error:
2555   {
2556     g_free (data);
2557     return GST_FLOW_ERROR;
2558   }
2559 }
2560 
2561 static gboolean
gst_qt_mux_downstream_is_seekable(GstQTMux * qtmux)2562 gst_qt_mux_downstream_is_seekable (GstQTMux * qtmux)
2563 {
2564   gboolean seekable = FALSE;
2565   GstQuery *query = gst_query_new_seeking (GST_FORMAT_BYTES);
2566 
2567   if (gst_pad_peer_query (GST_AGGREGATOR_SRC_PAD (qtmux), query)) {
2568     gst_query_parse_seeking (query, NULL, &seekable, NULL, NULL);
2569     GST_INFO_OBJECT (qtmux, "downstream is %sseekable", seekable ? "" : "not ");
2570   } else {
2571     /* have to assume seeking is not supported if query not handled downstream */
2572     GST_WARNING_OBJECT (qtmux, "downstream did not handle seeking query");
2573     seekable = FALSE;
2574   }
2575   gst_query_unref (query);
2576 
2577   return seekable;
2578 }
2579 
2580 /* Must be called with object lock */
2581 static void
gst_qt_mux_prepare_moov_recovery(GstQTMux * qtmux)2582 gst_qt_mux_prepare_moov_recovery (GstQTMux * qtmux)
2583 {
2584   GList *l;
2585   gboolean fail = FALSE;
2586   AtomFTYP *ftyp = NULL;
2587   GstBuffer *prefix = NULL;
2588 
2589   GST_DEBUG_OBJECT (qtmux, "Opening moov recovery file: %s",
2590       qtmux->moov_recov_file_path);
2591 
2592   qtmux->moov_recov_file = g_fopen (qtmux->moov_recov_file_path, "wb+");
2593   if (qtmux->moov_recov_file == NULL) {
2594     GST_WARNING_OBJECT (qtmux, "Failed to open moov recovery file in %s",
2595         qtmux->moov_recov_file_path);
2596     return;
2597   }
2598 
2599   gst_qt_mux_prepare_ftyp (qtmux, &ftyp, &prefix);
2600 
2601   if (!atoms_recov_write_headers (qtmux->moov_recov_file, ftyp, prefix,
2602           qtmux->moov, qtmux->timescale,
2603           g_list_length (GST_ELEMENT (qtmux)->sinkpads))) {
2604     GST_WARNING_OBJECT (qtmux, "Failed to write moov recovery file " "headers");
2605     goto fail;
2606   }
2607 
2608   atom_ftyp_free (ftyp);
2609   if (prefix)
2610     gst_buffer_unref (prefix);
2611 
2612   for (l = GST_ELEMENT_CAST (qtmux)->sinkpads; l; l = l->next) {
2613     GstQTMuxPad *qpad = (GstQTMuxPad *) l->data;
2614     /* write info for each stream */
2615     fail = atoms_recov_write_trak_info (qtmux->moov_recov_file, qpad->trak);
2616     if (fail) {
2617       GST_WARNING_OBJECT (qtmux, "Failed to write trak info to recovery "
2618           "file");
2619       break;
2620     }
2621   }
2622 
2623   return;
2624 
2625 fail:
2626   /* cleanup */
2627   fclose (qtmux->moov_recov_file);
2628   qtmux->moov_recov_file = NULL;
2629 }
2630 
2631 static guint64
prefill_get_block_index(GstQTMux * qtmux,GstQTMuxPad * qpad)2632 prefill_get_block_index (GstQTMux * qtmux, GstQTMuxPad * qpad)
2633 {
2634   switch (qpad->fourcc) {
2635     case FOURCC_apch:
2636     case FOURCC_apcn:
2637     case FOURCC_apcs:
2638     case FOURCC_apco:
2639     case FOURCC_ap4h:
2640     case FOURCC_ap4x:
2641     case FOURCC_c608:
2642     case FOURCC_c708:
2643       return qpad->sample_offset;
2644     case FOURCC_sowt:
2645     case FOURCC_twos:
2646       return gst_util_uint64_scale_ceil (qpad->sample_offset,
2647           qpad->expected_sample_duration_n,
2648           qpad->expected_sample_duration_d *
2649           atom_trak_get_timescale (qpad->trak));
2650     default:
2651       return -1;
2652   }
2653 }
2654 
2655 static guint
prefill_get_sample_size(GstQTMux * qtmux,GstQTMuxPad * qpad)2656 prefill_get_sample_size (GstQTMux * qtmux, GstQTMuxPad * qpad)
2657 {
2658   switch (qpad->fourcc) {
2659     case FOURCC_apch:
2660       if (((SampleTableEntryMP4V *) qpad->trak_ste)->height <= 480) {
2661         return 300000;
2662       } else if (((SampleTableEntryMP4V *) qpad->trak_ste)->height <= 576) {
2663         return 350000;
2664       } else if (((SampleTableEntryMP4V *) qpad->trak_ste)->height <= 720) {
2665         return 525000;
2666       } else if (((SampleTableEntryMP4V *) qpad->trak_ste)->height <= 1080) {
2667         return 1050000;
2668       } else if (((SampleTableEntryMP4V *) qpad->trak_ste)->height <= 2160) {
2669         return 4150000;
2670       } else {
2671         return 16600000;
2672       }
2673       break;
2674     case FOURCC_apcn:
2675       if (((SampleTableEntryMP4V *) qpad->trak_ste)->height <= 480) {
2676         return 200000;
2677       } else if (((SampleTableEntryMP4V *) qpad->trak_ste)->height <= 576) {
2678         return 250000;
2679       } else if (((SampleTableEntryMP4V *) qpad->trak_ste)->height <= 720) {
2680         return 350000;
2681       } else if (((SampleTableEntryMP4V *) qpad->trak_ste)->height <= 1080) {
2682         return 700000;
2683       } else if (((SampleTableEntryMP4V *) qpad->trak_ste)->height <= 2160) {
2684         return 2800000;
2685       } else {
2686         return 11200000;
2687       }
2688       break;
2689     case FOURCC_apcs:
2690       if (((SampleTableEntryMP4V *) qpad->trak_ste)->height <= 480) {
2691         return 150000;
2692       } else if (((SampleTableEntryMP4V *) qpad->trak_ste)->height <= 576) {
2693         return 200000;
2694       } else if (((SampleTableEntryMP4V *) qpad->trak_ste)->height <= 720) {
2695         return 250000;
2696       } else if (((SampleTableEntryMP4V *) qpad->trak_ste)->height <= 1080) {
2697         return 500000;
2698       } else if (((SampleTableEntryMP4V *) qpad->trak_ste)->height <= 2160) {
2699         return 2800000;
2700       } else {
2701         return 11200000;
2702       }
2703       break;
2704     case FOURCC_apco:
2705       if (((SampleTableEntryMP4V *) qpad->trak_ste)->height <= 480) {
2706         return 80000;
2707       } else if (((SampleTableEntryMP4V *) qpad->trak_ste)->height <= 576) {
2708         return 100000;
2709       } else if (((SampleTableEntryMP4V *) qpad->trak_ste)->height <= 720) {
2710         return 150000;
2711       } else if (((SampleTableEntryMP4V *) qpad->trak_ste)->height <= 1080) {
2712         return 250000;
2713       } else if (((SampleTableEntryMP4V *) qpad->trak_ste)->height <= 2160) {
2714         return 900000;
2715       } else {
2716         return 3600000;
2717       }
2718       break;
2719     case FOURCC_c608:
2720       /* We always write both cdat and cdt2 atom in prefill mode */
2721       return 20;
2722     case FOURCC_c708:{
2723       if (qpad->first_cc_sample_size == 0) {
2724         GstBuffer *buf =
2725             gst_aggregator_pad_peek_buffer (GST_AGGREGATOR_PAD (qpad));
2726         g_assert (buf != NULL);
2727         qpad->first_cc_sample_size = gst_buffer_get_size (buf);
2728         g_assert (qpad->first_cc_sample_size != 0);
2729         gst_buffer_unref (buf);
2730       }
2731       return qpad->first_cc_sample_size + 8;
2732     }
2733     case FOURCC_sowt:
2734     case FOURCC_twos:{
2735       guint64 block_idx;
2736       guint64 next_sample_offset;
2737 
2738       block_idx = prefill_get_block_index (qtmux, qpad);
2739       next_sample_offset =
2740           gst_util_uint64_scale (block_idx + 1,
2741           qpad->expected_sample_duration_d *
2742           atom_trak_get_timescale (qpad->trak),
2743           qpad->expected_sample_duration_n);
2744 
2745       return (next_sample_offset - qpad->sample_offset) * qpad->sample_size;
2746     }
2747     case FOURCC_ap4h:
2748     case FOURCC_ap4x:
2749     default:
2750       GST_ERROR_OBJECT (qtmux, "unsupported codec for pre-filling");
2751       return -1;
2752   }
2753 
2754   return -1;
2755 }
2756 
2757 static GstClockTime
prefill_get_next_timestamp(GstQTMux * qtmux,GstQTMuxPad * qpad)2758 prefill_get_next_timestamp (GstQTMux * qtmux, GstQTMuxPad * qpad)
2759 {
2760   switch (qpad->fourcc) {
2761     case FOURCC_apch:
2762     case FOURCC_apcn:
2763     case FOURCC_apcs:
2764     case FOURCC_apco:
2765     case FOURCC_ap4h:
2766     case FOURCC_ap4x:
2767     case FOURCC_c608:
2768     case FOURCC_c708:
2769       return gst_util_uint64_scale (qpad->sample_offset + 1,
2770           qpad->expected_sample_duration_d * GST_SECOND,
2771           qpad->expected_sample_duration_n);
2772     case FOURCC_sowt:
2773     case FOURCC_twos:{
2774       guint64 block_idx;
2775       guint64 next_sample_offset;
2776 
2777       block_idx = prefill_get_block_index (qtmux, qpad);
2778       next_sample_offset =
2779           gst_util_uint64_scale (block_idx + 1,
2780           qpad->expected_sample_duration_d *
2781           atom_trak_get_timescale (qpad->trak),
2782           qpad->expected_sample_duration_n);
2783 
2784       return gst_util_uint64_scale (next_sample_offset, GST_SECOND,
2785           atom_trak_get_timescale (qpad->trak));
2786     }
2787     default:
2788       GST_ERROR_OBJECT (qtmux, "unsupported codec for pre-filling");
2789       return -1;
2790   }
2791 
2792   return -1;
2793 }
2794 
2795 static GstBuffer *
prefill_raw_audio_prepare_buf_func(GstQTMuxPad * qtpad,GstBuffer * buf,GstQTMux * qtmux)2796 prefill_raw_audio_prepare_buf_func (GstQTMuxPad * qtpad, GstBuffer * buf,
2797     GstQTMux * qtmux)
2798 {
2799   guint64 block_idx;
2800   guint64 nsamples;
2801   GstClockTime input_timestamp;
2802   guint64 input_timestamp_distance;
2803 
2804   if (buf)
2805     gst_adapter_push (qtpad->raw_audio_adapter, buf);
2806 
2807   block_idx = gst_util_uint64_scale_ceil (qtpad->raw_audio_adapter_offset,
2808       qtpad->expected_sample_duration_n,
2809       qtpad->expected_sample_duration_d *
2810       atom_trak_get_timescale (qtpad->trak));
2811   nsamples =
2812       gst_util_uint64_scale (block_idx + 1,
2813       qtpad->expected_sample_duration_d * atom_trak_get_timescale (qtpad->trak),
2814       qtpad->expected_sample_duration_n) - qtpad->raw_audio_adapter_offset;
2815 
2816   if ((!gst_aggregator_pad_is_eos (GST_AGGREGATOR_PAD (qtpad))
2817           && gst_adapter_available (qtpad->raw_audio_adapter) <
2818           nsamples * qtpad->sample_size)
2819       || gst_adapter_available (qtpad->raw_audio_adapter) == 0) {
2820     return NULL;
2821   }
2822 
2823   input_timestamp =
2824       gst_adapter_prev_pts (qtpad->raw_audio_adapter,
2825       &input_timestamp_distance);
2826   if (input_timestamp != GST_CLOCK_TIME_NONE)
2827     input_timestamp +=
2828         gst_util_uint64_scale (input_timestamp_distance, GST_SECOND,
2829         qtpad->sample_size * atom_trak_get_timescale (qtpad->trak));
2830 
2831   buf =
2832       gst_adapter_take_buffer (qtpad->raw_audio_adapter,
2833       !gst_aggregator_pad_is_eos (GST_AGGREGATOR_PAD (qtpad)) ? nsamples *
2834       qtpad->sample_size : gst_adapter_available (qtpad->raw_audio_adapter));
2835   GST_BUFFER_PTS (buf) = input_timestamp;
2836   GST_BUFFER_DTS (buf) = GST_CLOCK_TIME_NONE;
2837   GST_BUFFER_DURATION (buf) = GST_CLOCK_TIME_NONE;
2838 
2839   qtpad->raw_audio_adapter_offset += nsamples;
2840 
2841   /* Check if we have yet another block of raw audio in the adapter */
2842   nsamples =
2843       gst_util_uint64_scale (block_idx + 2,
2844       qtpad->expected_sample_duration_d * atom_trak_get_timescale (qtpad->trak),
2845       qtpad->expected_sample_duration_n) - qtpad->raw_audio_adapter_offset;
2846   if (gst_adapter_available (qtpad->raw_audio_adapter) >=
2847       nsamples * qtpad->sample_size) {
2848     input_timestamp =
2849         gst_adapter_prev_pts (qtpad->raw_audio_adapter,
2850         &input_timestamp_distance);
2851     if (input_timestamp != GST_CLOCK_TIME_NONE)
2852       input_timestamp +=
2853           gst_util_uint64_scale (input_timestamp_distance, GST_SECOND,
2854           qtpad->sample_size * atom_trak_get_timescale (qtpad->trak));
2855     qtpad->raw_audio_adapter_pts = input_timestamp;
2856   } else {
2857     qtpad->raw_audio_adapter_pts = GST_CLOCK_TIME_NONE;
2858   }
2859 
2860   return buf;
2861 }
2862 
2863 /* Must be called with object lock */
2864 static void
find_video_sample_duration(GstQTMux * qtmux,guint * dur_n,guint * dur_d)2865 find_video_sample_duration (GstQTMux * qtmux, guint * dur_n, guint * dur_d)
2866 {
2867   GList *l;
2868 
2869   /* Find the (first) video track and assume that we have to output
2870    * in that size */
2871   for (l = GST_ELEMENT_CAST (qtmux)->sinkpads; l; l = l->next) {
2872     GstQTMuxPad *tmp_qpad = (GstQTMuxPad *) l->data;
2873 
2874     if (tmp_qpad->trak->is_video) {
2875       *dur_n = tmp_qpad->expected_sample_duration_n;
2876       *dur_d = tmp_qpad->expected_sample_duration_d;
2877       break;
2878     }
2879   }
2880 
2881   if (l == NULL) {
2882     GST_INFO_OBJECT (qtmux,
2883         "Found no video framerate, using 40ms audio buffers");
2884     *dur_n = 25;
2885     *dur_d = 1;
2886   }
2887 }
2888 
2889 /* Called when all pads are prerolled to adjust and  */
2890 static gboolean
prefill_update_sample_size(GstQTMux * qtmux,GstQTMuxPad * qpad)2891 prefill_update_sample_size (GstQTMux * qtmux, GstQTMuxPad * qpad)
2892 {
2893   switch (qpad->fourcc) {
2894     case FOURCC_apch:
2895     case FOURCC_apcn:
2896     case FOURCC_apcs:
2897     case FOURCC_apco:
2898     case FOURCC_ap4h:
2899     case FOURCC_ap4x:
2900     {
2901       guint sample_size = prefill_get_sample_size (qtmux, qpad);
2902       atom_trak_set_constant_size_samples (qpad->trak, sample_size);
2903       return TRUE;
2904     }
2905     case FOURCC_c608:
2906     case FOURCC_c708:
2907     {
2908       guint sample_size = prefill_get_sample_size (qtmux, qpad);
2909       /* We need a "valid" duration */
2910       find_video_sample_duration (qtmux, &qpad->expected_sample_duration_n,
2911           &qpad->expected_sample_duration_d);
2912       atom_trak_set_constant_size_samples (qpad->trak, sample_size);
2913       return TRUE;
2914     }
2915     case FOURCC_sowt:
2916     case FOURCC_twos:{
2917       find_video_sample_duration (qtmux, &qpad->expected_sample_duration_n,
2918           &qpad->expected_sample_duration_d);
2919       /* Set a prepare_buf_func that ensures this */
2920       qpad->prepare_buf_func = prefill_raw_audio_prepare_buf_func;
2921       qpad->raw_audio_adapter = gst_adapter_new ();
2922       qpad->raw_audio_adapter_offset = 0;
2923       qpad->raw_audio_adapter_pts = GST_CLOCK_TIME_NONE;
2924 
2925       return TRUE;
2926     }
2927     default:
2928       return TRUE;
2929   }
2930 }
2931 
2932 /* Only called at startup when doing the "fake" iteration of all tracks in order
2933  * to prefill the sample tables in the header.  */
2934 static GstQTMuxPad *
find_best_pad_prefill_start(GstQTMux * qtmux)2935 find_best_pad_prefill_start (GstQTMux * qtmux)
2936 {
2937   GstQTMuxPad *best_pad = NULL;
2938 
2939   /* If interleave limits have been specified and the current pad is within
2940    * those interleave limits, pick that one, otherwise let's try to figure out
2941    * the next best one. */
2942 
2943   if (qtmux->current_pad &&
2944       (qtmux->interleave_bytes != 0 || qtmux->interleave_time != 0) &&
2945       (qtmux->interleave_bytes == 0
2946           || qtmux->current_chunk_size <= qtmux->interleave_bytes)
2947       && (qtmux->interleave_time == 0
2948           || qtmux->current_chunk_duration <= qtmux->interleave_time)
2949       && qtmux->mux_mode != GST_QT_MUX_MODE_FRAGMENTED) {
2950 
2951     if (qtmux->current_pad->total_duration < qtmux->reserved_max_duration) {
2952       best_pad = qtmux->current_pad;
2953     }
2954   } else {
2955     GST_OBJECT_LOCK (qtmux);
2956     if (GST_ELEMENT_CAST (qtmux)->sinkpads->next) {
2957       /* Attempt to try another pad if we have one. Otherwise use the only pad
2958        * present */
2959       best_pad = qtmux->current_pad = NULL;
2960     }
2961     GST_OBJECT_UNLOCK (qtmux);
2962   }
2963 
2964   /* The next best pad is the one which has the lowest timestamp and hasn't
2965    * exceeded the reserved max duration */
2966   if (!best_pad) {
2967     GList *l;
2968     GstClockTime best_time = GST_CLOCK_TIME_NONE;
2969 
2970     GST_OBJECT_LOCK (qtmux);
2971     for (l = GST_ELEMENT_CAST (qtmux)->sinkpads; l; l = l->next) {
2972       GstQTMuxPad *qtpad = (GstQTMuxPad *) l->data;
2973       GstClockTime timestamp;
2974 
2975       if (qtpad->total_duration >= qtmux->reserved_max_duration)
2976         continue;
2977 
2978       timestamp = qtpad->total_duration;
2979 
2980       if (best_pad == NULL ||
2981           !GST_CLOCK_TIME_IS_VALID (best_time) || timestamp < best_time) {
2982         best_pad = qtpad;
2983         best_time = timestamp;
2984       }
2985     }
2986     GST_OBJECT_UNLOCK (qtmux);
2987   }
2988 
2989   return best_pad;
2990 }
2991 
2992 /* Called when starting the file in prefill_mode to figure out all the entries
2993  * of the header based on the input stream and reserved maximum duration.
2994  *
2995  * The _actual_ header (i.e. with the proper duration and trimmed sample tables)
2996  * will be updated and written on EOS. */
2997 static gboolean
gst_qt_mux_prefill_samples(GstQTMux * qtmux)2998 gst_qt_mux_prefill_samples (GstQTMux * qtmux)
2999 {
3000   GstQTMuxPad *qpad;
3001   GList *l;
3002   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
3003 
3004   /* Update expected sample sizes/durations as needed, this is for raw
3005    * audio where samples are actual audio samples. */
3006   GST_OBJECT_LOCK (qtmux);
3007   for (l = GST_ELEMENT_CAST (qtmux)->sinkpads; l; l = l->next) {
3008     GstQTMuxPad *qpad = (GstQTMuxPad *) l->data;
3009 
3010     if (!prefill_update_sample_size (qtmux, qpad)) {
3011       GST_OBJECT_UNLOCK (qtmux);
3012       return FALSE;
3013     }
3014   }
3015   GST_OBJECT_UNLOCK (qtmux);
3016 
3017   if (qtmux_klass->format == GST_QT_MUX_FORMAT_QT ||
3018       qtmux->force_create_timecode_trak) {
3019     /* For the first sample check/update timecode as needed. We do that before
3020      * all actual samples as the code in gst_qt_mux_add_buffer() does it with
3021      * initial buffer directly, not with last_buf */
3022     GST_OBJECT_LOCK (qtmux);
3023     for (l = GST_ELEMENT_CAST (qtmux)->sinkpads; l; l = l->next) {
3024       GstQTMuxPad *qpad = (GstQTMuxPad *) l->data;
3025       GstBuffer *buffer =
3026           gst_aggregator_pad_peek_buffer (GST_AGGREGATOR_PAD (qpad));
3027       GstVideoTimeCodeMeta *tc_meta;
3028 
3029       if (buffer && (tc_meta = gst_buffer_get_video_time_code_meta (buffer))
3030           && qpad->trak->is_video) {
3031         GstVideoTimeCode *tc = &tc_meta->tc;
3032 
3033         qpad->tc_trak = atom_trak_new (qtmux->context);
3034         atom_moov_add_trak (qtmux->moov, qpad->tc_trak);
3035 
3036         qpad->trak->tref = atom_tref_new (FOURCC_tmcd);
3037         atom_tref_add_entry (qpad->trak->tref, qpad->tc_trak->tkhd.track_ID);
3038 
3039         atom_trak_set_timecode_type (qpad->tc_trak, qtmux->context,
3040             qpad->trak->mdia.mdhd.time_info.timescale, tc);
3041 
3042         atom_trak_add_samples (qpad->tc_trak, 1, 1, 4,
3043             qtmux->mdat_size, FALSE, 0);
3044 
3045         qpad->tc_pos = qtmux->mdat_size;
3046         qpad->first_tc = gst_video_time_code_copy (tc);
3047         qpad->first_pts = GST_BUFFER_PTS (buffer);
3048 
3049         qtmux->current_chunk_offset = -1;
3050         qtmux->current_chunk_size = 0;
3051         qtmux->current_chunk_duration = 0;
3052         qtmux->mdat_size += 4;
3053       }
3054       if (buffer)
3055         gst_buffer_unref (buffer);
3056     }
3057     GST_OBJECT_UNLOCK (qtmux);
3058   }
3059 
3060   while ((qpad = find_best_pad_prefill_start (qtmux))) {
3061     GstClockTime timestamp, next_timestamp, duration;
3062     guint nsamples, sample_size;
3063     guint64 chunk_offset;
3064     gint64 scaled_duration;
3065     gint64 pts_offset = 0;
3066     gboolean sync = FALSE;
3067     TrakBufferEntryInfo sample_entry;
3068 
3069     sample_size = prefill_get_sample_size (qtmux, qpad);
3070 
3071     if (sample_size == -1) {
3072       return FALSE;
3073     }
3074 
3075     if (!qpad->samples)
3076       qpad->samples = g_array_new (FALSE, FALSE, sizeof (TrakBufferEntryInfo));
3077 
3078     timestamp = qpad->total_duration;
3079     next_timestamp = prefill_get_next_timestamp (qtmux, qpad);
3080     duration = next_timestamp - timestamp;
3081 
3082     if (qpad->first_ts == GST_CLOCK_TIME_NONE)
3083       qpad->first_ts = timestamp;
3084     if (qpad->first_dts == GST_CLOCK_TIME_NONE)
3085       qpad->first_dts = timestamp;
3086 
3087     if (qtmux->current_pad != qpad || qtmux->current_chunk_offset == -1) {
3088       qtmux->current_pad = qpad;
3089       if (qtmux->current_chunk_offset == -1)
3090         qtmux->current_chunk_offset = qtmux->mdat_size;
3091       else
3092         qtmux->current_chunk_offset += qtmux->current_chunk_size;
3093       qtmux->current_chunk_size = 0;
3094       qtmux->current_chunk_duration = 0;
3095     }
3096     if (qpad->sample_size)
3097       nsamples = sample_size / qpad->sample_size;
3098     else
3099       nsamples = 1;
3100     qpad->last_dts = timestamp;
3101     scaled_duration = gst_util_uint64_scale_round (timestamp + duration,
3102         atom_trak_get_timescale (qpad->trak),
3103         GST_SECOND) - gst_util_uint64_scale_round (timestamp,
3104         atom_trak_get_timescale (qpad->trak), GST_SECOND);
3105 
3106     qtmux->current_chunk_size += sample_size;
3107     qtmux->current_chunk_duration += duration;
3108     qpad->total_bytes += sample_size;
3109 
3110     chunk_offset = qtmux->current_chunk_offset;
3111 
3112     /* I-frame only, no frame reordering */
3113     sync = FALSE;
3114     pts_offset = 0;
3115 
3116     if (qtmux->current_chunk_duration > qtmux->longest_chunk
3117         || !GST_CLOCK_TIME_IS_VALID (qtmux->longest_chunk)) {
3118       qtmux->longest_chunk = qtmux->current_chunk_duration;
3119     }
3120 
3121     sample_entry.track_id = qpad->trak->tkhd.track_ID;
3122     sample_entry.nsamples = nsamples;
3123     sample_entry.delta = scaled_duration / nsamples;
3124     sample_entry.size = sample_size / nsamples;
3125     sample_entry.chunk_offset = chunk_offset;
3126     sample_entry.pts_offset = pts_offset;
3127     sample_entry.sync = sync;
3128     sample_entry.do_pts = TRUE;
3129     g_array_append_val (qpad->samples, sample_entry);
3130     atom_trak_add_samples (qpad->trak, nsamples, scaled_duration / nsamples,
3131         sample_size / nsamples, chunk_offset, sync, pts_offset);
3132 
3133     qpad->total_duration = next_timestamp;
3134     qtmux->mdat_size += sample_size;
3135     qpad->sample_offset += nsamples;
3136   }
3137 
3138   return TRUE;
3139 }
3140 
3141 static GstFlowReturn
gst_qt_mux_start_file(GstQTMux * qtmux)3142 gst_qt_mux_start_file (GstQTMux * qtmux)
3143 {
3144   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
3145   GstFlowReturn ret = GST_FLOW_OK;
3146   GstCaps *caps;
3147   GstClockTime reserved_max_duration;
3148   guint reserved_bytes_per_sec_per_trak;
3149   GList *l;
3150 
3151   GST_DEBUG_OBJECT (qtmux, "starting file");
3152 
3153   GST_OBJECT_LOCK (qtmux);
3154   reserved_max_duration = qtmux->reserved_max_duration;
3155   reserved_bytes_per_sec_per_trak = qtmux->reserved_bytes_per_sec_per_trak;
3156   GST_OBJECT_UNLOCK (qtmux);
3157 
3158   caps =
3159       gst_caps_copy (gst_pad_get_pad_template_caps (GST_AGGREGATOR_SRC_PAD
3160           (qtmux)));
3161   /* qtmux has structure with and without variant, remove all but the first */
3162   g_assert (gst_caps_truncate (caps));
3163   gst_aggregator_set_src_caps (GST_AGGREGATOR (qtmux), caps);
3164   gst_caps_unref (caps);
3165 
3166   /* Default is 'normal' mode */
3167   qtmux->mux_mode = GST_QT_MUX_MODE_MOOV_AT_END;
3168 
3169   /* Require a sensible fragment duration when muxing
3170    * using the ISML muxer */
3171   if (qtmux_klass->format == GST_QT_MUX_FORMAT_ISML &&
3172       qtmux->fragment_duration == 0)
3173     goto invalid_isml;
3174 
3175   if (qtmux->fragment_duration > 0) {
3176     qtmux->mux_mode = GST_QT_MUX_MODE_FRAGMENTED;
3177     if (qtmux->streamable
3178         && qtmux->fragment_mode == GST_QT_MUX_FRAGMENT_DASH_OR_MSS) {
3179       qtmux->fragment_mode = GST_QT_MUX_FRAGMENT_STREAMABLE;
3180     }
3181   } else if (qtmux->fast_start) {
3182     qtmux->mux_mode = GST_QT_MUX_MODE_FAST_START;
3183   } else if (reserved_max_duration != GST_CLOCK_TIME_NONE) {
3184     if (reserved_max_duration == 0) {
3185       GST_ELEMENT_ERROR (qtmux, STREAM, MUX,
3186           ("reserved-max-duration of 0 is not allowed"), (NULL));
3187       return GST_FLOW_ERROR;
3188     }
3189     if (qtmux->reserved_prefill)
3190       qtmux->mux_mode = GST_QT_MUX_MODE_ROBUST_RECORDING_PREFILL;
3191     else
3192       qtmux->mux_mode = GST_QT_MUX_MODE_ROBUST_RECORDING;
3193   }
3194 
3195   qtmux->downstream_seekable = gst_qt_mux_downstream_is_seekable (qtmux);
3196   switch (qtmux->mux_mode) {
3197     case GST_QT_MUX_MODE_MOOV_AT_END:
3198       break;
3199     case GST_QT_MUX_MODE_ROBUST_RECORDING:
3200       /* We have to be able to seek to rewrite the mdat header, or any
3201        * moov atom we write will not be visible in the file, because an
3202        * MDAT with 0 as the size covers the rest of the file. A file
3203        * with no moov is not playable, so error out now. */
3204       if (!qtmux->downstream_seekable) {
3205         GST_ELEMENT_ERROR (qtmux, STREAM, MUX,
3206             ("Downstream is not seekable - will not be able to create a playable file"),
3207             (NULL));
3208         return GST_FLOW_ERROR;
3209       }
3210       if (qtmux->reserved_moov_update_period == GST_CLOCK_TIME_NONE) {
3211         GST_WARNING_OBJECT (qtmux,
3212             "Robust muxing requires reserved-moov-update-period to be set");
3213       }
3214       break;
3215     case GST_QT_MUX_MODE_FAST_START:
3216       break;                    /* Don't need seekability, ignore */
3217     case GST_QT_MUX_MODE_FRAGMENTED:
3218       if (qtmux->fragment_mode == GST_QT_MUX_FRAGMENT_STREAMABLE)
3219         break;
3220       if (!qtmux->downstream_seekable) {
3221         if (qtmux->fragment_mode == GST_QT_MUX_FRAGMENT_DASH_OR_MSS) {
3222           GST_WARNING_OBJECT (qtmux, "downstream is not seekable, but "
3223               "streamable=false. Will ignore that and create streamable output "
3224               "instead");
3225           qtmux->streamable = TRUE;
3226           g_object_notify (G_OBJECT (qtmux), "streamable");
3227           qtmux->fragment_mode = GST_QT_MUX_FRAGMENT_STREAMABLE;
3228         }
3229       }
3230       break;
3231     case GST_QT_MUX_MODE_ROBUST_RECORDING_PREFILL:
3232       if (!qtmux->downstream_seekable) {
3233         GST_WARNING_OBJECT (qtmux,
3234             "downstream is not seekable, will not be able "
3235             "to trim samples table at the end if less than reserved-duration is "
3236             "recorded");
3237       }
3238       break;
3239   }
3240 
3241   GST_OBJECT_LOCK (qtmux);
3242 
3243   if (qtmux->timescale == 0) {
3244     guint32 suggested_timescale = 0;
3245 
3246     /* Calculate a reasonable timescale for the moov:
3247      * If there is video, it is the biggest video track timescale or an even
3248      * multiple of it if it's smaller than 1800.
3249      * Otherwise it is 1800 */
3250     for (l = GST_ELEMENT_CAST (qtmux)->sinkpads; l; l = l->next) {
3251       GstQTMuxPad *qpad = (GstQTMuxPad *) l->data;
3252 
3253       if (!qpad->trak)
3254         continue;
3255 
3256       /* not video */
3257       if (!qpad->trak->mdia.minf.vmhd)
3258         continue;
3259 
3260       suggested_timescale =
3261           MAX (qpad->trak->mdia.mdhd.time_info.timescale, suggested_timescale);
3262     }
3263 
3264     if (suggested_timescale == 0)
3265       suggested_timescale = 1800;
3266 
3267     while (suggested_timescale < 1800)
3268       suggested_timescale *= 2;
3269 
3270     qtmux->timescale = suggested_timescale;
3271   }
3272 
3273   /* Set width/height/timescale of any closed caption tracks to that of the
3274    * first video track */
3275   {
3276     guint video_width = 0, video_height = 0;
3277     guint32 video_timescale = 0;
3278     GList *l;
3279 
3280     for (l = GST_ELEMENT_CAST (qtmux)->sinkpads; l; l = l->next) {
3281       GstQTMuxPad *qpad = (GstQTMuxPad *) l->data;
3282 
3283       if (!qpad->trak)
3284         continue;
3285 
3286       /* Not closed caption */
3287       if (qpad->trak->mdia.hdlr.handler_type != FOURCC_clcp)
3288         continue;
3289 
3290       if (video_width == 0 || video_height == 0 || video_timescale == 0) {
3291         GList *l2;
3292 
3293         for (l2 = GST_ELEMENT_CAST (qtmux)->sinkpads; l2; l2 = l2->next) {
3294           GstQTMuxPad *qpad2 = (GstQTMuxPad *) l2->data;
3295 
3296           if (!qpad2->trak)
3297             continue;
3298 
3299           /* not video */
3300           if (!qpad2->trak->mdia.minf.vmhd)
3301             continue;
3302 
3303           video_width = qpad2->trak->tkhd.width;
3304           video_height = qpad2->trak->tkhd.height;
3305           video_timescale = qpad2->trak->mdia.mdhd.time_info.timescale;
3306         }
3307       }
3308 
3309       qpad->trak->tkhd.width = video_width << 16;
3310       qpad->trak->tkhd.height = video_height << 16;
3311       qpad->trak->mdia.mdhd.time_info.timescale = video_timescale;
3312     }
3313   }
3314 
3315   /* initialize our moov recovery file */
3316   if (qtmux->moov_recov_file_path) {
3317     gst_qt_mux_prepare_moov_recovery (qtmux);
3318   }
3319 
3320   /* Make sure the first time we update the moov, we'll
3321    * include any tagsetter tags */
3322   qtmux->tags_changed = TRUE;
3323 
3324   GST_OBJECT_UNLOCK (qtmux);
3325 
3326   /*
3327    * send mdat header if already needed, and mark position for later update.
3328    * We don't send ftyp now if we are on fast start mode, because we can
3329    * better fine tune using the information we gather to create the whole moov
3330    * atom.
3331    */
3332   switch (qtmux->mux_mode) {
3333     case GST_QT_MUX_MODE_MOOV_AT_END:
3334       ret = gst_qt_mux_prepare_and_send_ftyp (qtmux);
3335       if (ret != GST_FLOW_OK)
3336         break;
3337 
3338       /* Store this as the mdat offset for later updating
3339        * when we write the moov */
3340       qtmux->mdat_pos = qtmux->header_size;
3341       /* extended atom in case we go over 4GB while writing and need
3342        * the full 64-bit atom */
3343       if (qtmux->downstream_seekable)
3344         ret =
3345             gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0, TRUE,
3346             FALSE);
3347       break;
3348     case GST_QT_MUX_MODE_ROBUST_RECORDING:
3349       ret = gst_qt_mux_prepare_and_send_ftyp (qtmux);
3350       if (ret != GST_FLOW_OK)
3351         break;
3352 
3353       /* Pad ftyp out to an 8-byte boundary before starting the moov
3354        * ping pong region. It should be well less than 1 disk sector,
3355        * unless there's a bajillion compatible types listed,
3356        * but let's be sure the free atom doesn't cross a sector
3357        * boundary anyway */
3358       if (qtmux->header_size % 8) {
3359         /* Extra 8 bytes for the padding free atom header */
3360         guint padding = (guint) (16 - (qtmux->header_size % 8));
3361         GST_LOG_OBJECT (qtmux, "Rounding ftyp by %u bytes", padding);
3362         ret =
3363             gst_qt_mux_send_free_atom (qtmux, &qtmux->header_size, padding,
3364             FALSE);
3365         if (ret != GST_FLOW_OK)
3366           return ret;
3367       }
3368 
3369       /* Store this as the moov offset for later updating.
3370        * We record mdat position below */
3371       qtmux->moov_pos = qtmux->header_size;
3372 
3373       /* Set up the initial 'ping' state of the ping-pong buffers */
3374       qtmux->reserved_moov_first_active = TRUE;
3375 
3376       gst_qt_mux_configure_moov (qtmux);
3377       gst_qt_mux_setup_metadata (qtmux);
3378       /* Empty free atom to begin, starting on an 8-byte boundary */
3379       ret = gst_qt_mux_send_free_atom (qtmux, &qtmux->header_size, 8, FALSE);
3380       if (ret != GST_FLOW_OK)
3381         return ret;
3382       /* Moov header, not padded yet */
3383       ret = gst_qt_mux_send_moov (qtmux, &qtmux->header_size, 0, FALSE, FALSE);
3384       if (ret != GST_FLOW_OK)
3385         return ret;
3386       /* The moov we just sent contains the 'base' size of the moov, before
3387        * we put in any time-dependent per-trak data. Use that to make
3388        * a good estimate of how much extra to reserve */
3389       /* Calculate how much space to reserve for our MOOV atom.
3390        * We actually reserve twice that, for ping-pong buffers */
3391       qtmux->base_moov_size = qtmux->last_moov_size;
3392       GST_LOG_OBJECT (qtmux, "Base moov size is %u before any indexes",
3393           qtmux->base_moov_size);
3394       qtmux->reserved_moov_size = qtmux->base_moov_size +
3395           gst_util_uint64_scale (reserved_max_duration,
3396           reserved_bytes_per_sec_per_trak *
3397           atom_moov_get_trak_count (qtmux->moov), GST_SECOND);
3398 
3399       /* Need space for at least 4 atom headers. More really, but
3400        * this as an absolute minimum */
3401       if (qtmux->reserved_moov_size < 4 * 8)
3402         goto reserved_moov_too_small;
3403 
3404       GST_DEBUG_OBJECT (qtmux, "reserving header area of size %u",
3405           2 * qtmux->reserved_moov_size + 16);
3406 
3407       GST_OBJECT_LOCK (qtmux);
3408       qtmux->reserved_duration_remaining =
3409           gst_util_uint64_scale (qtmux->reserved_moov_size -
3410           qtmux->base_moov_size, GST_SECOND,
3411           reserved_bytes_per_sec_per_trak *
3412           atom_moov_get_trak_count (qtmux->moov));
3413       GST_OBJECT_UNLOCK (qtmux);
3414 
3415       /* Now that we know how much reserved space is targeted,
3416        * output a free atom to fill the extra reserved */
3417       ret = gst_qt_mux_send_free_atom (qtmux, &qtmux->header_size,
3418           qtmux->reserved_moov_size - qtmux->base_moov_size, FALSE);
3419       if (ret != GST_FLOW_OK)
3420         return ret;
3421 
3422       /* Then a free atom containing 'pong' buffer, with an
3423        * extra 8 bytes to account for the free atom header itself */
3424       ret = gst_qt_mux_send_free_atom (qtmux, &qtmux->header_size,
3425           qtmux->reserved_moov_size + 8, FALSE);
3426       if (ret != GST_FLOW_OK)
3427         return ret;
3428 
3429       /* extra atoms go after the free/moov(s), before the mdat */
3430       ret =
3431           gst_qt_mux_send_extra_atoms (qtmux, TRUE, &qtmux->header_size, FALSE);
3432       if (ret != GST_FLOW_OK)
3433         return ret;
3434 
3435       qtmux->mdat_pos = qtmux->header_size;
3436       /* extended atom in case we go over 4GB while writing and need
3437        * the full 64-bit atom */
3438       ret =
3439           gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0, TRUE,
3440           FALSE);
3441       break;
3442     case GST_QT_MUX_MODE_ROBUST_RECORDING_PREFILL:
3443     {
3444       guint32 atom_size;
3445 
3446       ret = gst_qt_mux_prepare_and_send_ftyp (qtmux);
3447       if (ret != GST_FLOW_OK)
3448         break;
3449 
3450       /* Store this as the moov offset for later updating.
3451        * We record mdat position below */
3452       qtmux->moov_pos = qtmux->header_size;
3453 
3454       if (!gst_qt_mux_prefill_samples (qtmux)) {
3455         GST_ELEMENT_ERROR (qtmux, STREAM, MUX,
3456             ("Unsupported codecs or configuration for prefill mode"), (NULL));
3457 
3458         return GST_FLOW_ERROR;
3459       }
3460 
3461       gst_qt_mux_update_global_statistics (qtmux);
3462       gst_qt_mux_configure_moov (qtmux);
3463       gst_qt_mux_update_edit_lists (qtmux);
3464       gst_qt_mux_setup_metadata (qtmux);
3465 
3466       /* Moov header with pre-filled samples */
3467       ret = gst_qt_mux_send_moov (qtmux, &qtmux->header_size, 0, FALSE, FALSE);
3468       if (ret != GST_FLOW_OK)
3469         return ret;
3470 
3471       GST_OBJECT_LOCK (qtmux);
3472       atom_size = 12 * g_list_length (GST_ELEMENT (qtmux)->sinkpads) + 8;
3473       GST_OBJECT_UNLOCK (qtmux);
3474 
3475       /* last_moov_size now contains the full size of the moov, moov_pos the
3476        * position. This allows us to rewrite it in the very end as needed */
3477       qtmux->reserved_moov_size = qtmux->last_moov_size + atom_size;
3478 
3479       /* Send an additional free atom at the end so we definitely have space
3480        * to rewrite the moov header at the end and remove the samples that
3481        * were not actually written */
3482       ret =
3483           gst_qt_mux_send_free_atom (qtmux, &qtmux->header_size, atom_size,
3484           FALSE);
3485       if (ret != GST_FLOW_OK)
3486         return ret;
3487 
3488       /* extra atoms go after the free/moov(s), before the mdat */
3489       ret =
3490           gst_qt_mux_send_extra_atoms (qtmux, TRUE, &qtmux->header_size, FALSE);
3491       if (ret != GST_FLOW_OK)
3492         return ret;
3493 
3494       qtmux->mdat_pos = qtmux->header_size;
3495 
3496       /* And now send the mdat header */
3497       ret =
3498           gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size,
3499           qtmux->mdat_size, TRUE, FALSE);
3500 
3501       /* chunks position is set relative to the first byte of the
3502        * MDAT atom payload. Set the overall offset into the file */
3503       atom_moov_chunks_set_offset (qtmux->moov, qtmux->header_size);
3504 
3505       {
3506         gst_qt_mux_seek_to (qtmux, qtmux->moov_pos);
3507 
3508         ret = gst_qt_mux_send_moov (qtmux, NULL, 0, FALSE, FALSE);
3509         if (ret != GST_FLOW_OK)
3510           return ret;
3511 
3512         gst_qt_mux_seek_to (qtmux, qtmux->header_size);
3513       }
3514 
3515       GST_OBJECT_LOCK (qtmux);
3516       qtmux->current_chunk_size = 0;
3517       qtmux->current_chunk_duration = 0;
3518       qtmux->current_chunk_offset = -1;
3519       qtmux->mdat_size = 0;
3520       qtmux->current_pad = NULL;
3521       qtmux->longest_chunk = GST_CLOCK_TIME_NONE;
3522 
3523       for (l = GST_ELEMENT_CAST (qtmux)->sinkpads; l; l = l->next) {
3524         GstQTMuxPad *qtpad = (GstQTMuxPad *) l->data;
3525 
3526         qtpad->total_bytes = 0;
3527         qtpad->total_duration = 0;
3528         qtpad->first_dts = qtpad->first_ts = GST_CLOCK_TIME_NONE;
3529         qtpad->last_dts = GST_CLOCK_TIME_NONE;
3530         qtpad->sample_offset = 0;
3531       }
3532       GST_OBJECT_UNLOCK (qtmux);
3533 
3534       break;
3535     }
3536     case GST_QT_MUX_MODE_FAST_START:
3537       GST_OBJECT_LOCK (qtmux);
3538       qtmux->fast_start_file = g_fopen (qtmux->fast_start_file_path, "wb+");
3539       if (!qtmux->fast_start_file)
3540         goto open_failed;
3541       GST_OBJECT_UNLOCK (qtmux);
3542       /* send a dummy buffer for preroll */
3543       ret = gst_qt_mux_send_buffer (qtmux, gst_buffer_new (), NULL, FALSE);
3544       break;
3545     case GST_QT_MUX_MODE_FRAGMENTED:
3546       ret = gst_qt_mux_prepare_and_send_ftyp (qtmux);
3547       if (ret != GST_FLOW_OK)
3548         break;
3549 
3550       GST_DEBUG_OBJECT (qtmux, "fragment duration %d ms, writing headers",
3551           qtmux->fragment_duration);
3552       qtmux->fragment_sequence = 0;
3553       if (qtmux->fragment_mode == GST_QT_MUX_FRAGMENT_FIRST_MOOV_THEN_FINALISE) {
3554         /* Store this as the mdat offset for later updating
3555          * when we write the moov */
3556         qtmux->mdat_pos = qtmux->header_size;
3557         /* extended atom in case we go over 4GB while writing and need
3558          * the full 64-bit atom */
3559         ret =
3560             gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0, TRUE,
3561             FALSE);
3562         if (ret != GST_FLOW_OK)
3563           return ret;
3564       } else {
3565         /* store the moov pos so we can update the duration later
3566          * in non-streamable mode */
3567         qtmux->moov_pos = qtmux->header_size;
3568 
3569         /* prepare moov and/or tags */
3570         qtmux->fragment_sequence++;
3571         gst_qt_mux_configure_moov (qtmux);
3572         gst_qt_mux_setup_metadata (qtmux);
3573         ret =
3574             gst_qt_mux_send_moov (qtmux, &qtmux->header_size, 0, FALSE, FALSE);
3575         if (ret != GST_FLOW_OK)
3576           return ret;
3577         /* extra atoms */
3578         ret =
3579             gst_qt_mux_send_extra_atoms (qtmux, TRUE, &qtmux->header_size,
3580             FALSE);
3581         if (ret != GST_FLOW_OK)
3582           break;
3583       }
3584       /* prepare index if not streamable, or overwriting with moov */
3585       if (qtmux->fragment_mode == GST_QT_MUX_FRAGMENT_DASH_OR_MSS)
3586         qtmux->mfra = atom_mfra_new (qtmux->context);
3587       break;
3588   }
3589 
3590   return ret;
3591   /* ERRORS */
3592 invalid_isml:
3593   {
3594     GST_ELEMENT_ERROR (qtmux, STREAM, MUX,
3595         ("Cannot create an ISML file with 0 fragment duration"), (NULL));
3596     return GST_FLOW_ERROR;
3597   }
3598 reserved_moov_too_small:
3599   {
3600     GST_ELEMENT_ERROR (qtmux, STREAM, MUX,
3601         ("Not enough reserved space for creating headers"), (NULL));
3602     return GST_FLOW_ERROR;
3603   }
3604 open_failed:
3605   {
3606     GST_ELEMENT_ERROR (qtmux, RESOURCE, OPEN_READ_WRITE,
3607         (("Could not open temporary file \"%s\""),
3608             qtmux->fast_start_file_path), GST_ERROR_SYSTEM);
3609     GST_OBJECT_UNLOCK (qtmux);
3610     return GST_FLOW_ERROR;
3611   }
3612 }
3613 
3614 static GstFlowReturn
gst_qt_mux_send_last_buffers(GstQTMux * qtmux)3615 gst_qt_mux_send_last_buffers (GstQTMux * qtmux)
3616 {
3617   GstFlowReturn ret = GST_FLOW_OK;
3618   GList *sinkpads, *l;
3619 
3620   GST_OBJECT_LOCK (qtmux);
3621   sinkpads = g_list_copy_deep (GST_ELEMENT_CAST (qtmux)->sinkpads,
3622       (GCopyFunc) gst_object_ref, NULL);
3623   GST_OBJECT_UNLOCK (qtmux);
3624 
3625   for (l = sinkpads; l; l = l->next) {
3626     GstQTMuxPad *qtpad = (GstQTMuxPad *) l->data;
3627 
3628     /* avoid add_buffer complaining if not negotiated
3629      * in which case no buffers either, so skipping */
3630     if (!qtpad->fourcc) {
3631       GST_DEBUG_OBJECT (qtmux, "Pad %s has never had buffers",
3632           GST_PAD_NAME (qtpad));
3633       continue;
3634     }
3635 
3636     /* send last buffer; also flushes possibly queued buffers/ts */
3637     GST_DEBUG_OBJECT (qtmux, "Sending the last buffer for pad %s",
3638         GST_PAD_NAME (qtpad));
3639     ret = gst_qt_mux_add_buffer (qtmux, qtpad, NULL);
3640     if (ret != GST_FLOW_OK) {
3641       GST_WARNING_OBJECT (qtmux, "Failed to send last buffer for %s, "
3642           "flow return: %s", GST_PAD_NAME (qtpad), gst_flow_get_name (ret));
3643     }
3644   }
3645 
3646   g_list_free_full (sinkpads, gst_object_unref);
3647 
3648   return ret;
3649 }
3650 
3651 static void
gst_qt_mux_update_global_statistics(GstQTMux * qtmux)3652 gst_qt_mux_update_global_statistics (GstQTMux * qtmux)
3653 {
3654   GList *l;
3655 
3656   /* for setting some subtitles fields */
3657   guint max_width = 0;
3658   guint max_height = 0;
3659 
3660   qtmux->first_ts = qtmux->last_dts = GST_CLOCK_TIME_NONE;
3661 
3662   GST_OBJECT_LOCK (qtmux);
3663   for (l = GST_ELEMENT_CAST (qtmux)->sinkpads; l; l = l->next) {
3664     GstQTMuxPad *qtpad = (GstQTMuxPad *) l->data;
3665 
3666     if (!qtpad->fourcc) {
3667       GST_DEBUG_OBJECT (qtmux, "Pad %s has never had buffers",
3668           GST_PAD_NAME (qtpad));
3669       continue;
3670     }
3671 
3672     /* having flushed above, can check for buffers now */
3673     if (GST_CLOCK_TIME_IS_VALID (qtpad->first_ts)) {
3674       GstClockTime first_pts_in = qtpad->first_ts;
3675       /* it should be, since we got first_ts by adding adjustment
3676        * to a positive incoming PTS */
3677       if (qtpad->dts_adjustment <= first_pts_in)
3678         first_pts_in -= qtpad->dts_adjustment;
3679       /* determine max stream duration */
3680       if (!GST_CLOCK_TIME_IS_VALID (qtmux->last_dts)
3681           || qtpad->last_dts > qtmux->last_dts) {
3682         qtmux->last_dts = qtpad->last_dts;
3683       }
3684       if (!GST_CLOCK_TIME_IS_VALID (qtmux->first_ts)
3685           || first_pts_in < qtmux->first_ts) {
3686         /* we need the original incoming PTS here, as this first_ts
3687          * is used in update_edit_lists to construct the edit list that arrange
3688          * for sync'ed streams.  The first_ts is most likely obtained from
3689          * some (audio) stream with 0 dts_adjustment and initial 0 PTS,
3690          * so it makes no difference, though it matters in other cases */
3691         qtmux->first_ts = first_pts_in;
3692       }
3693     }
3694 
3695     /* subtitles need to know the video width/height,
3696      * it is stored shifted 16 bits to the left according to the
3697      * spec */
3698     max_width = MAX (max_width, (qtpad->trak->tkhd.width >> 16));
3699     max_height = MAX (max_height, (qtpad->trak->tkhd.height >> 16));
3700 
3701     /* update average bitrate of streams if needed */
3702     {
3703       guint32 avgbitrate = 0;
3704       guint32 maxbitrate = qtpad->max_bitrate;
3705 
3706       if (qtpad->avg_bitrate)
3707         avgbitrate = qtpad->avg_bitrate;
3708       else if (qtpad->total_duration > 0)
3709         avgbitrate = (guint32) gst_util_uint64_scale_round (qtpad->total_bytes,
3710             8 * GST_SECOND, qtpad->total_duration);
3711 
3712       atom_trak_update_bitrates (qtpad->trak, avgbitrate, maxbitrate);
3713     }
3714   }
3715   GST_OBJECT_UNLOCK (qtmux);
3716 
3717   /* need to update values on subtitle traks now that we know the
3718    * max width and height */
3719   GST_OBJECT_LOCK (qtmux);
3720   for (l = GST_ELEMENT_CAST (qtmux)->sinkpads; l; l = l->next) {
3721     GstQTMuxPad *qtpad = (GstQTMuxPad *) l->data;
3722 
3723     if (!qtpad->fourcc) {
3724       GST_DEBUG_OBJECT (qtmux, "Pad %s has never had buffers",
3725           GST_PAD_NAME (qtpad));
3726       continue;
3727     }
3728 
3729     if (qtpad->fourcc == FOURCC_tx3g) {
3730       atom_trak_tx3g_update_dimension (qtpad->trak, max_width, max_height);
3731     }
3732   }
3733   GST_OBJECT_UNLOCK (qtmux);
3734 }
3735 
3736 /* Called after gst_qt_mux_update_global_statistics() updates the
3737  * first_ts tracking, to create/set edit lists for delayed streams */
3738 static void
gst_qt_mux_update_edit_lists(GstQTMux * qtmux)3739 gst_qt_mux_update_edit_lists (GstQTMux * qtmux)
3740 {
3741   GList *l;
3742 
3743   GST_DEBUG_OBJECT (qtmux, "Media first ts selected: %" GST_TIME_FORMAT,
3744       GST_TIME_ARGS (qtmux->first_ts));
3745   /* add/update EDTSs for late streams. configure_moov will have
3746    * set the trak durations above by summing the sample tables,
3747    * here we extend that if needing to insert an empty segment */
3748   GST_OBJECT_LOCK (qtmux);
3749   for (l = GST_ELEMENT_CAST (qtmux)->sinkpads; l; l = l->next) {
3750     GstQTMuxPad *qtpad = (GstQTMuxPad *) l->data;
3751 
3752     atom_trak_edts_clear (qtpad->trak);
3753 
3754     if (GST_CLOCK_TIME_IS_VALID (qtpad->first_ts)) {
3755       guint32 lateness = 0;
3756       guint32 duration = qtpad->trak->tkhd.duration;
3757       gboolean has_gap;
3758 
3759       has_gap = (qtpad->first_ts > (qtmux->first_ts + qtpad->dts_adjustment));
3760 
3761       if (has_gap) {
3762         GstClockTime diff, trak_lateness;
3763 
3764         diff = qtpad->first_ts - (qtmux->first_ts + qtpad->dts_adjustment);
3765         lateness = gst_util_uint64_scale_round (diff,
3766             qtmux->timescale, GST_SECOND);
3767 
3768         /* Allow up to 1 trak timescale unit of lateness, Such a small
3769          * timestamp/duration can't be represented by the trak-specific parts
3770          * of the headers anyway, so it's irrelevantly small */
3771         trak_lateness = gst_util_uint64_scale (diff,
3772             atom_trak_get_timescale (qtpad->trak), GST_SECOND);
3773 
3774         if (trak_lateness > 0 && diff > qtmux->start_gap_threshold) {
3775           GST_DEBUG_OBJECT (qtmux,
3776               "Pad %s is a late stream by %" GST_TIME_FORMAT,
3777               GST_PAD_NAME (qtpad), GST_TIME_ARGS (diff));
3778 
3779           atom_trak_set_elst_entry (qtpad->trak, 0, lateness, (guint32) - 1,
3780               (guint32) (1 * 65536.0));
3781         }
3782       }
3783 
3784       /* Always write an edit list for the whole track. In general this is not
3785        * necessary except for the case of having a gap or DTS adjustment but
3786        * it allows to give the whole track's duration in the usually more
3787        * accurate media timescale
3788        */
3789       {
3790         GstClockTime ctts = 0;
3791         guint32 media_start;
3792 
3793         if (qtpad->first_ts > qtpad->first_dts)
3794           ctts = qtpad->first_ts - qtpad->first_dts;
3795 
3796         media_start = gst_util_uint64_scale_round (ctts,
3797             atom_trak_get_timescale (qtpad->trak), GST_SECOND);
3798 
3799         /* atom_trak_set_elst_entry() has a quirk - if the edit list
3800          * is empty because there's no gap added above, this call
3801          * will not replace index 1, it will create the entry at index 0.
3802          * Luckily, that's exactly what we want here */
3803         atom_trak_set_elst_entry (qtpad->trak, 1, duration, media_start,
3804             (guint32) (1 * 65536.0));
3805       }
3806 
3807       /* need to add the empty time to the trak duration */
3808       duration += lateness;
3809       qtpad->trak->tkhd.duration = duration;
3810       if (qtpad->tc_trak) {
3811         qtpad->tc_trak->tkhd.duration = duration;
3812         qtpad->tc_trak->mdia.mdhd.time_info.duration = duration;
3813       }
3814 
3815       /* And possibly grow the moov duration */
3816       if (duration > qtmux->moov->mvhd.time_info.duration) {
3817         qtmux->moov->mvhd.time_info.duration = duration;
3818         qtmux->moov->mvex.mehd.fragment_duration = duration;
3819       }
3820     }
3821   }
3822   GST_OBJECT_UNLOCK (qtmux);
3823 }
3824 
3825 static GstFlowReturn
gst_qt_mux_update_timecode(GstQTMux * qtmux,GstQTMuxPad * qtpad)3826 gst_qt_mux_update_timecode (GstQTMux * qtmux, GstQTMuxPad * qtpad)
3827 {
3828   GstBuffer *buf;
3829   GstMapInfo map;
3830   guint64 offset = qtpad->tc_pos;
3831   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
3832 
3833   if (qtmux_klass->format != GST_QT_MUX_FORMAT_QT &&
3834       !qtmux->force_create_timecode_trak)
3835     return GST_FLOW_OK;
3836 
3837   g_assert (qtpad->tc_pos != -1);
3838 
3839   gst_qt_mux_seek_to (qtmux, offset);
3840 
3841   buf = gst_buffer_new_and_alloc (4);
3842   gst_buffer_map (buf, &map, GST_MAP_WRITE);
3843 
3844   GST_WRITE_UINT32_BE (map.data,
3845       gst_video_time_code_frames_since_daily_jam (qtpad->first_tc));
3846   gst_buffer_unmap (buf, &map);
3847 
3848   /* Reset this value, so the timecode won't be re-rewritten */
3849   qtpad->tc_pos = -1;
3850 
3851   return gst_qt_mux_send_buffer (qtmux, buf, &offset, FALSE);
3852 }
3853 
3854 static void
unref_buffer_if_set(GstBuffer * buffer)3855 unref_buffer_if_set (GstBuffer * buffer)
3856 {
3857   if (buffer)
3858     gst_buffer_unref (buffer);
3859 }
3860 
3861 static GstFlowReturn
gst_qtmux_push_mdat_stored_buffers(GstQTMux * qtmux)3862 gst_qtmux_push_mdat_stored_buffers (GstQTMux * qtmux)
3863 {
3864   GstFlowReturn ret = GST_FLOW_OK;
3865   GList *l = qtmux->output_buffers;
3866   guint64 mdat_header_size = 0, size = 0;
3867 
3868   for (; l; l = g_list_next (l)) {
3869     GstBuffer *buf = (GstBuffer *) l->data;
3870 
3871     size += gst_buffer_get_size (buf);
3872   }
3873 
3874   if (size == 0)
3875     return GST_FLOW_OK;
3876 
3877   GST_DEBUG_OBJECT (qtmux, "Pushing stored buffers of size %" G_GUINT64_FORMAT
3878       " current mdat size %" G_GUINT64_FORMAT, size, qtmux->mdat_size);
3879 
3880   ret = gst_qt_mux_send_mdat_header (qtmux, &mdat_header_size, size,
3881       size > MDAT_LARGE_FILE_LIMIT, FALSE);
3882 
3883   /* reset chunking */
3884   qtmux->current_chunk_size = 0;
3885   qtmux->current_chunk_duration = 0;
3886   qtmux->current_chunk_offset = -1;
3887 
3888   /* on the first mdat, we need to offset the header by the mdat header size
3889    * as the moov offset is in relation to the first data byte inside the first
3890    * mdat */
3891   if (qtmux->mdat_size == 0)
3892     qtmux->header_size += mdat_header_size;
3893   qtmux->mdat_size += mdat_header_size;
3894 
3895   l = qtmux->output_buffers;
3896   while (ret == GST_FLOW_OK && l) {
3897     GstBuffer *buf = (GstBuffer *) l->data;
3898 
3899     ret = gst_qt_mux_send_buffer (qtmux, buf, &qtmux->mdat_size, TRUE);
3900 
3901     l->data = NULL;
3902     l = g_list_next (l);
3903   }
3904 
3905   g_list_free_full (qtmux->output_buffers,
3906       (GDestroyNotify) unref_buffer_if_set);
3907   qtmux->output_buffers = NULL;
3908 
3909   return ret;
3910 }
3911 
3912 static GstFlowReturn
gst_qt_mux_stop_file(GstQTMux * qtmux)3913 gst_qt_mux_stop_file (GstQTMux * qtmux)
3914 {
3915   gboolean ret = GST_FLOW_OK;
3916   guint64 offset = 0, size = 0;
3917   gboolean large_file;
3918   GList *sinkpads, *l;
3919 
3920   GST_DEBUG_OBJECT (qtmux, "Updating remaining values and sending last data");
3921 
3922   /* pushing last buffers for each pad */
3923   if ((ret = gst_qt_mux_send_last_buffers (qtmux)) != GST_FLOW_OK)
3924     return ret;
3925 
3926   if (qtmux->mux_mode == GST_QT_MUX_MODE_FRAGMENTED
3927       && qtmux->fragment_mode == GST_QT_MUX_FRAGMENT_STREAMABLE) {
3928     /* Streamable mode; no need to write duration or MFRA */
3929     GST_DEBUG_OBJECT (qtmux, "streamable file; nothing to stop");
3930     return GST_FLOW_OK;
3931   }
3932 
3933   gst_qt_mux_update_global_statistics (qtmux);
3934 
3935   GST_OBJECT_LOCK (qtmux);
3936   sinkpads = g_list_copy_deep (GST_ELEMENT_CAST (qtmux)->sinkpads,
3937       (GCopyFunc) gst_object_ref, NULL);
3938   GST_OBJECT_UNLOCK (qtmux);
3939 
3940   for (l = sinkpads; l; l = l->next) {
3941     GstQTMuxPad *qtpad = (GstQTMuxPad *) l->data;
3942 
3943     if (qtpad->tc_pos != -1) {
3944       /* File is being stopped and timecode hasn't been updated. Update it now
3945        * with whatever we have */
3946       ret = gst_qt_mux_update_timecode (qtmux, qtpad);
3947       if (ret != GST_FLOW_OK) {
3948         g_list_free_full (sinkpads, gst_object_unref);
3949         return ret;
3950       }
3951     }
3952   }
3953 
3954   g_list_free_full (sinkpads, gst_object_unref);
3955 
3956   switch (qtmux->mux_mode) {
3957     case GST_QT_MUX_MODE_MOOV_AT_END:{
3958       if (!qtmux->downstream_seekable) {
3959         ret = gst_qtmux_push_mdat_stored_buffers (qtmux);
3960         if (ret != GST_FLOW_OK)
3961           return ret;
3962       }
3963       break;
3964     }
3965     case GST_QT_MUX_MODE_FRAGMENTED:{
3966       GstBuffer *buf;
3967       GstClockTime duration;
3968 
3969       if (qtmux->mfra) {
3970         guint8 *data = NULL;
3971 
3972         size = offset = 0;
3973 
3974         GST_DEBUG_OBJECT (qtmux, "adding mfra");
3975         if (!atom_mfra_copy_data (qtmux->mfra, &data, &size, &offset))
3976           goto serialize_error;
3977         buf = _gst_buffer_new_take_data (data, offset);
3978         ret = gst_qt_mux_send_buffer (qtmux, buf, NULL, FALSE);
3979         if (ret != GST_FLOW_OK)
3980           return ret;
3981       }
3982 
3983       /* only mvex duration is updated,
3984        * mvhd should be consistent with empty moov
3985        * (but TODO maybe some clients do not handle that well ?) */
3986       duration = gst_util_uint64_scale_round (qtmux->last_dts, qtmux->timescale,
3987           GST_SECOND);
3988 
3989       GST_DEBUG_OBJECT (qtmux,
3990           "writing moov with mvhd/mvex duration %" GST_TIME_FORMAT,
3991           GST_TIME_ARGS (qtmux->last_dts));
3992       if (qtmux->fragment_mode == GST_QT_MUX_FRAGMENT_FIRST_MOOV_THEN_FINALISE) {
3993         /* seek and overwrite the original moov with an invalid atom */
3994         /* XXX: assumes an extended size atom is not used for the moov */
3995 
3996         qtmux->moov->mvhd.time_info.duration = duration;
3997 
3998         /* (+4) skip the skip bytes */
3999         gst_qt_mux_seek_to (qtmux, qtmux->moov_pos + 4);
4000 
4001         /* invalidate the previous moov */
4002         buf = gst_buffer_new_wrapped (g_strdup ("h"), 1);
4003         ret = gst_qt_mux_send_buffer (qtmux, buf, NULL, FALSE);
4004         if (ret != GST_FLOW_OK)
4005           return ret;
4006 
4007         /* we want to rewrite the first mdat to cover the entire data before
4008          * this moov */
4009         qtmux->mdat_size = qtmux->header_size - qtmux->mdat_pos - 16;
4010 
4011         gst_qt_mux_seek_to (qtmux, qtmux->mdat_pos);
4012 
4013         ret = gst_qt_mux_update_mdat_size (qtmux, qtmux->mdat_pos,
4014             qtmux->mdat_size, NULL, FALSE);
4015         if (ret != GST_FLOW_OK)
4016           return ret;
4017 
4018         /* Then write the moov atom as in moov-at-end *without* updating the
4019          * mdat size */
4020         gst_qt_mux_seek_to (qtmux, qtmux->header_size);
4021 
4022         /* revert back to moov-at-end assumptions where header_size is the
4023          * size up to the first byte of data in the mdat */
4024         qtmux->header_size = qtmux->mdat_pos + 16;
4025         break;
4026       } else {
4027         qtmux->moov->mvex.mehd.fragment_duration = duration;
4028 
4029         /* seek and rewrite the header */
4030         gst_qt_mux_seek_to (qtmux, qtmux->moov_pos);
4031         /* no need to seek back */
4032         return gst_qt_mux_send_moov (qtmux, NULL, 0, FALSE, FALSE);
4033       }
4034     }
4035     case GST_QT_MUX_MODE_ROBUST_RECORDING:{
4036       ret = gst_qt_mux_robust_recording_rewrite_moov (qtmux);
4037       if (G_UNLIKELY (ret != GST_FLOW_OK))
4038         return ret;
4039       /* Finalise by writing the final size into the mdat. Up until now
4040        * it's been 0, which means 'rest of the file'
4041        * No need to seek back after this, we won't write any more */
4042       return gst_qt_mux_update_mdat_size (qtmux, qtmux->mdat_pos,
4043           qtmux->mdat_size, NULL, TRUE);
4044     }
4045     case GST_QT_MUX_MODE_ROBUST_RECORDING_PREFILL:{
4046       GList *l;
4047       guint32 next_track_id = qtmux->moov->mvhd.next_track_id;
4048 
4049       GST_OBJECT_LOCK (qtmux);
4050       for (l = GST_ELEMENT_CAST (qtmux)->sinkpads; l; l = l->next) {
4051         GstQTMuxPad *qpad = (GstQTMuxPad *) l->data;
4052         guint64 block_idx;
4053         AtomSTBL *stbl = &qpad->trak->mdia.minf.stbl;
4054 
4055         /* Get the block index of the last sample we wrote, not of the next
4056          * sample we would write */
4057         block_idx = prefill_get_block_index (qtmux, qpad);
4058 
4059         /* stts */
4060         if (block_idx > 0) {
4061           STTSEntry *entry;
4062           guint64 nsamples = 0;
4063           gint i, n;
4064 
4065           n = atom_array_get_len (&stbl->stts.entries);
4066           for (i = 0; i < n; i++) {
4067             entry = &atom_array_index (&stbl->stts.entries, i);
4068             if (nsamples + entry->sample_count >= qpad->sample_offset) {
4069               entry->sample_count = qpad->sample_offset - nsamples;
4070               stbl->stts.entries.len = i + 1;
4071               break;
4072             }
4073             nsamples += entry->sample_count;
4074           }
4075           g_assert (i < n);
4076         } else {
4077           stbl->stts.entries.len = 0;
4078         }
4079 
4080         /* stsz */
4081         {
4082           g_assert (stbl->stsz.entries.len == 0);
4083           stbl->stsz.table_size = qpad->sample_offset;
4084         }
4085 
4086         /* stco/stsc */
4087         {
4088           gint i, n;
4089           guint64 nsamples = 0;
4090           gint chunk_index = 0;
4091           const TrakBufferEntryInfo *sample_entry;
4092 
4093           if (block_idx > 0) {
4094             sample_entry =
4095                 &g_array_index (qpad->samples, TrakBufferEntryInfo,
4096                 block_idx - 1);
4097 
4098             n = stbl->stco64.entries.len;
4099             for (i = 0; i < n; i++) {
4100               guint64 *entry = &atom_array_index (&stbl->stco64.entries, i);
4101 
4102               if (*entry == sample_entry->chunk_offset) {
4103                 stbl->stco64.entries.len = i + 1;
4104                 chunk_index = i + 1;
4105                 break;
4106               }
4107             }
4108             g_assert (i < n);
4109             g_assert (chunk_index > 0);
4110 
4111             n = stbl->stsc.entries.len;
4112             for (i = 0; i < n; i++) {
4113               STSCEntry *entry = &atom_array_index (&stbl->stsc.entries, i);
4114 
4115               if (entry->first_chunk >= chunk_index)
4116                 break;
4117 
4118               if (i > 0) {
4119                 nsamples +=
4120                     (entry->first_chunk - atom_array_index (&stbl->stsc.entries,
4121                         i -
4122                         1).first_chunk) * atom_array_index (&stbl->stsc.entries,
4123                     i - 1).samples_per_chunk;
4124               }
4125             }
4126             g_assert (i <= n);
4127 
4128             if (i > 0) {
4129               STSCEntry *prev_entry =
4130                   &atom_array_index (&stbl->stsc.entries, i - 1);
4131               nsamples +=
4132                   (chunk_index -
4133                   prev_entry->first_chunk) * prev_entry->samples_per_chunk;
4134               if (qpad->sample_offset - nsamples > 0) {
4135                 stbl->stsc.entries.len = i;
4136                 atom_stsc_add_new_entry (&stbl->stsc, chunk_index,
4137                     qpad->sample_offset - nsamples, stbl->stsd.n_entries);
4138               } else {
4139                 stbl->stsc.entries.len = i;
4140                 stbl->stco64.entries.len--;
4141               }
4142             } else {
4143               /* Everything in a single chunk */
4144               stbl->stsc.entries.len = 0;
4145               atom_stsc_add_new_entry (&stbl->stsc, chunk_index,
4146                   qpad->sample_offset, stbl->stsd.n_entries);
4147             }
4148           } else {
4149             stbl->stco64.entries.len = 0;
4150             stbl->stsc.entries.len = 0;
4151           }
4152         }
4153 
4154         {
4155           GList *walk2;
4156 
4157           for (walk2 = qtmux->moov->mvex.trexs; walk2; walk2 = walk2->next) {
4158             AtomTREX *trex = walk2->data;
4159 
4160             if (trex->track_ID == qpad->trak->tkhd.track_ID) {
4161               trex->track_ID = next_track_id;
4162               break;
4163             }
4164           }
4165 
4166           qpad->trak->tkhd.track_ID = next_track_id++;
4167         }
4168       }
4169       GST_OBJECT_UNLOCK (qtmux);
4170 
4171       qtmux->moov->mvhd.next_track_id = next_track_id;
4172 
4173       gst_qt_mux_update_global_statistics (qtmux);
4174       gst_qt_mux_configure_moov (qtmux);
4175 
4176       gst_qt_mux_update_edit_lists (qtmux);
4177 
4178       /* Check if any gap edit lists were added. We don't have any space
4179        * reserved for this in the moov and the pre-finalized moov would have
4180        * broken A/V synchronization. Error out here now
4181        */
4182       GST_OBJECT_LOCK (qtmux);
4183       for (l = GST_ELEMENT_CAST (qtmux)->sinkpads; l; l = l->next) {
4184         GstQTMuxPad *qpad = (GstQTMuxPad *) l->data;
4185 
4186         if (qpad->trak->edts
4187             && g_slist_length (qpad->trak->edts->elst.entries) > 1) {
4188           GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
4189               ("Can't support gaps in prefill mode"));
4190 
4191           GST_OBJECT_UNLOCK (qtmux);
4192 
4193           return GST_FLOW_ERROR;
4194         }
4195       }
4196       GST_OBJECT_UNLOCK (qtmux);
4197 
4198       gst_qt_mux_setup_metadata (qtmux);
4199       atom_moov_chunks_set_offset (qtmux->moov, qtmux->header_size);
4200 
4201       {
4202         gst_qt_mux_seek_to (qtmux, qtmux->moov_pos);
4203 
4204         ret =
4205             gst_qt_mux_send_moov (qtmux, NULL, qtmux->reserved_moov_size, FALSE,
4206             FALSE);
4207         if (ret != GST_FLOW_OK)
4208           return ret;
4209 
4210         if (qtmux->reserved_moov_size > qtmux->last_moov_size) {
4211           ret =
4212               gst_qt_mux_send_free_atom (qtmux, NULL,
4213               qtmux->reserved_moov_size - qtmux->last_moov_size, TRUE);
4214         }
4215 
4216         if (ret != GST_FLOW_OK)
4217           return ret;
4218       }
4219 
4220       ret = gst_qt_mux_update_mdat_size (qtmux, qtmux->mdat_pos,
4221           qtmux->mdat_size, NULL, FALSE);
4222       return ret;
4223     }
4224     default:
4225       break;
4226   }
4227 
4228   /* Moov-at-end or fast-start mode from here down */
4229   gst_qt_mux_configure_moov (qtmux);
4230 
4231   gst_qt_mux_update_edit_lists (qtmux);
4232 
4233   /* tags into file metadata */
4234   gst_qt_mux_setup_metadata (qtmux);
4235 
4236   large_file = (qtmux->mdat_size > MDAT_LARGE_FILE_LIMIT);
4237 
4238   switch (qtmux->mux_mode) {
4239     case GST_QT_MUX_MODE_FAST_START:{
4240       /* if faststart, update the offset of the atoms in the movie with the offset
4241        * that the movie headers before mdat will cause.
4242        * Also, send the ftyp */
4243       offset = size = 0;
4244 
4245       ret = gst_qt_mux_prepare_and_send_ftyp (qtmux);
4246       if (ret != GST_FLOW_OK) {
4247         goto ftyp_error;
4248       }
4249       /* copy into NULL to obtain size */
4250       if (!atom_moov_copy_data (qtmux->moov, NULL, &size, &offset))
4251         goto serialize_error;
4252       GST_DEBUG_OBJECT (qtmux, "calculated moov atom size %" G_GUINT64_FORMAT,
4253           offset);
4254       offset += qtmux->header_size + (large_file ? 16 : 8);
4255 
4256       /* sum up with the extra atoms size */
4257       ret = gst_qt_mux_send_extra_atoms (qtmux, FALSE, &offset, FALSE);
4258       if (ret != GST_FLOW_OK)
4259         return ret;
4260       break;
4261     }
4262     default:
4263       offset = qtmux->header_size;
4264       break;
4265   }
4266 
4267   /* Now that we know the size of moov + extra atoms, we can adjust
4268    * the chunk offsets stored into the moov */
4269   atom_moov_chunks_set_offset (qtmux->moov, offset);
4270 
4271   /* write out moov and extra atoms */
4272   /* note: as of this point, we no longer care about tracking written data size,
4273    * since there is no more use for it anyway */
4274   ret = gst_qt_mux_send_moov (qtmux, NULL, 0, FALSE, FALSE);
4275   if (ret != GST_FLOW_OK)
4276     return ret;
4277 
4278   /* extra atoms */
4279   ret = gst_qt_mux_send_extra_atoms (qtmux, TRUE, NULL, FALSE);
4280   if (ret != GST_FLOW_OK)
4281     return ret;
4282 
4283   switch (qtmux->mux_mode) {
4284     case GST_QT_MUX_MODE_MOOV_AT_END:
4285     {
4286       if (qtmux->downstream_seekable) {
4287         /* mdat needs update iff not using faststart */
4288         GST_DEBUG_OBJECT (qtmux, "updating mdat size at position %"
4289             G_GUINT64_FORMAT " to size %" G_GUINT64_FORMAT, qtmux->mdat_pos,
4290             qtmux->mdat_size);
4291         ret = gst_qt_mux_update_mdat_size (qtmux, qtmux->mdat_pos,
4292             qtmux->mdat_size, NULL, FALSE);
4293         /* note; no seeking back to the end of file is done,
4294          * since we no longer write anything anyway */
4295       }
4296       break;
4297     }
4298     case GST_QT_MUX_MODE_FAST_START:
4299     {
4300       /* send mdat atom and move buffered data into it */
4301       /* mdat_size = accumulated (buffered data) */
4302       ret = gst_qt_mux_send_mdat_header (qtmux, NULL, qtmux->mdat_size,
4303           large_file, FALSE);
4304       if (ret != GST_FLOW_OK)
4305         return ret;
4306       ret = gst_qt_mux_send_buffered_data (qtmux, NULL);
4307       if (ret != GST_FLOW_OK)
4308         return ret;
4309       break;
4310     }
4311     case GST_QT_MUX_MODE_FRAGMENTED:
4312       g_assert (qtmux->fragment_mode ==
4313           GST_QT_MUX_FRAGMENT_FIRST_MOOV_THEN_FINALISE);
4314       break;
4315     default:
4316       g_assert_not_reached ();
4317   }
4318 
4319   return ret;
4320 
4321   /* ERRORS */
4322 serialize_error:
4323   {
4324     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
4325         ("Failed to serialize moov"));
4326     return GST_FLOW_ERROR;
4327   }
4328 ftyp_error:
4329   {
4330     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL), ("Failed to send ftyp"));
4331     return GST_FLOW_ERROR;
4332   }
4333 }
4334 
4335 static gboolean
gst_qtmux_pad_update_fragment_duration(GstElement * element,GstPad * pad,gpointer user_data)4336 gst_qtmux_pad_update_fragment_duration (GstElement * element, GstPad * pad,
4337     gpointer user_data)
4338 {
4339   GstQTMux *qtmux = (GstQTMux *) element;
4340   GstQTMuxPad *qt_pad = GST_QT_MUX_PAD (pad);
4341 
4342   qt_pad->fragment_duration = gst_util_uint64_scale (qtmux->fragment_duration,
4343       atom_trak_get_timescale (qt_pad->trak), 1000);
4344 
4345   return TRUE;
4346 }
4347 
4348 static gboolean
gst_qtmux_pad_collect_traf(GstElement * element,GstPad * pad,gpointer user_data)4349 gst_qtmux_pad_collect_traf (GstElement * element, GstPad * pad,
4350     gpointer user_data)
4351 {
4352   GstQTMuxPad *qt_pad = GST_QT_MUX_PAD (pad);
4353   AtomMOOF *moof = user_data;
4354 
4355   GST_TRACE_OBJECT (pad, "adding traf %p to moof %p", qt_pad->traf, moof);
4356 
4357   /* takes ownership */
4358   if (qt_pad->traf)
4359     atom_moof_add_traf (moof, qt_pad->traf);
4360   qt_pad->traf = NULL;
4361 
4362   return TRUE;
4363 }
4364 
4365 static GstFlowReturn
gst_qt_mux_pad_fragment_add_buffer(GstQTMux * qtmux,GstQTMuxPad * pad,GstBuffer * buf,gboolean force,guint32 nsamples,gint64 dts,guint32 delta,guint32 size,guint64 chunk_offset,gboolean sync,gint64 pts_offset)4366 gst_qt_mux_pad_fragment_add_buffer (GstQTMux * qtmux, GstQTMuxPad * pad,
4367     GstBuffer * buf, gboolean force, guint32 nsamples, gint64 dts,
4368     guint32 delta, guint32 size, guint64 chunk_offset, gboolean sync,
4369     gint64 pts_offset)
4370 {
4371   GstFlowReturn ret = GST_FLOW_OK;
4372   guint index = 0;
4373 
4374   GST_LOG_OBJECT (pad, "%p %u %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT,
4375       pad->traf, force, qtmux->current_chunk_offset, chunk_offset);
4376 
4377   /* setup if needed */
4378   if (G_UNLIKELY (!pad->traf || force))
4379     goto init;
4380 
4381 flush:
4382   /* flush pad fragment if threshold reached,
4383    * or at new keyframe if we should be minding those in the first place */
4384   if (G_UNLIKELY (force || (sync && pad->sync) ||
4385           pad->fragment_duration < (gint64) delta)) {
4386 
4387     if (qtmux->fragment_mode == GST_QT_MUX_FRAGMENT_FIRST_MOOV_THEN_FINALISE) {
4388       if (qtmux->fragment_sequence == 0) {
4389         /* the first fragment which we write as a moov */
4390         guint64 orig_offset;
4391         guint64 offset = orig_offset = qtmux->mdat_pos + 16 + qtmux->mdat_size;
4392         guint64 chunk_increase, buf_size;
4393         AtomMOOF *moof;
4394 
4395         GST_LOG_OBJECT (qtmux, "current file offset calculated to be %"
4396             G_GUINT64_FORMAT " based on mdat pos %" G_GUINT64_FORMAT
4397             " and size %" G_GUINT64_FORMAT, offset, qtmux->mdat_pos,
4398             qtmux->mdat_size);
4399 
4400         moof = atom_moof_new (qtmux->context, qtmux->fragment_sequence);
4401         gst_element_foreach_sink_pad (GST_ELEMENT (qtmux),
4402             gst_qtmux_pad_collect_traf, moof);
4403         atom_moof_free (moof);
4404 
4405         ret = gst_qt_mux_update_mdat_size (qtmux, qtmux->mdat_pos,
4406             qtmux->mdat_size, NULL, FALSE);
4407         if (ret != GST_FLOW_OK)
4408           return ret;
4409 
4410         /* seek back to the end of the file */
4411         qtmux->moov_pos = offset;
4412         gst_qt_mux_seek_to (qtmux, qtmux->moov_pos);
4413 
4414         /* update moov data */
4415         gst_qt_mux_update_global_statistics (qtmux);
4416         gst_qt_mux_configure_moov_full (qtmux, TRUE, qtmux->timescale);
4417         gst_qt_mux_update_edit_lists (qtmux);
4418         gst_qt_mux_setup_metadata (qtmux);
4419         /* chunk offset is the offset to the first byte inside the mdat */
4420         atom_moov_chunks_set_offset (qtmux->moov, qtmux->mdat_pos + 16);
4421 
4422         ret = gst_qt_mux_send_moov (qtmux, &offset, 0, TRUE, FALSE);
4423         if (ret != GST_FLOW_OK)
4424           return ret;
4425 
4426         /* for the continuation in fragments, header_size is the tracking write
4427          * position */
4428         qtmux->header_size = offset;
4429         qtmux->moof_mdat_pos = 0;
4430 
4431         buf_size = (buf ? gst_buffer_get_size (buf) : 0);
4432 
4433         chunk_increase = offset - orig_offset + 16;
4434         /* we need to undo the addition to qtmux->current_chunk_size of this
4435          * buffer performed in gst_qt_mux_register_buffer_in_chunk() */
4436         chunk_increase += qtmux->current_chunk_size - buf_size;
4437         GST_LOG_OBJECT (qtmux, "We think we have written %" G_GUINT64_FORMAT
4438             " including a moov and mdat header of %" G_GUINT64_FORMAT
4439             ". mangling this buffer's chunk offset from %" G_GUINT64_FORMAT
4440             " to %" G_GUINT64_FORMAT, qtmux->header_size,
4441             offset - orig_offset + 16, chunk_offset,
4442             chunk_offset + chunk_increase);
4443         /* this is the offset for the current chunk that is applied to all subsequent chunks */
4444         chunk_offset += chunk_increase;
4445         qtmux->current_chunk_offset += chunk_increase;
4446         qtmux->current_chunk_size = buf_size;
4447         GST_LOG_OBJECT (qtmux, "change next chunk offset to %" G_GUINT64_FORMAT
4448             " and size to %" G_GUINT64_FORMAT, qtmux->current_chunk_offset,
4449             qtmux->current_chunk_size);
4450 
4451         gst_element_foreach_sink_pad (GST_ELEMENT (qtmux),
4452             gst_qtmux_pad_update_fragment_duration, NULL);
4453       } else {
4454         AtomMOOF *moof;
4455         guint64 size = 0, offset = 0;
4456         guint8 *data = NULL;
4457         GstBuffer *moof_buffer;
4458         guint64 moof_size = 0, buf_size;
4459         guint64 chunk_increase;
4460 
4461         /* rewrite the mdat header */
4462         ret = gst_qt_mux_update_mdat_size (qtmux, qtmux->moof_mdat_pos,
4463             qtmux->header_size - qtmux->moof_mdat_pos - 16, NULL, FALSE);
4464         if (ret != GST_FLOW_OK)
4465           return ret;
4466 
4467         /* reseek back to the current position */
4468         gst_qt_mux_seek_to (qtmux, qtmux->header_size);
4469 
4470         moof = atom_moof_new (qtmux->context, qtmux->fragment_sequence);
4471         gst_element_foreach_sink_pad (GST_ELEMENT (qtmux),
4472             gst_qtmux_pad_collect_traf, moof);
4473         atom_moof_set_base_offset (moof, qtmux->moof_mdat_pos);
4474         atom_moof_copy_data (moof, &data, &size, &offset);
4475         moof_buffer = _gst_buffer_new_take_data (data, offset);
4476         moof_size = gst_buffer_get_size (moof_buffer);
4477 
4478         atom_moof_free (moof);
4479         /* now we know where moof ends up, update offset in tfra */
4480         if (pad->tfra)
4481           atom_tfra_update_offset (pad->tfra, qtmux->header_size);
4482 
4483         GST_LOG_OBJECT (qtmux, "writing moof of size %" G_GUINT64_FORMAT,
4484             moof_size);
4485         ret =
4486             gst_qt_mux_send_buffer (qtmux, moof_buffer, &qtmux->header_size,
4487             FALSE);
4488         if (ret != GST_FLOW_OK)
4489           goto moof_send_error;
4490         qtmux->moof_mdat_pos = 0;
4491 
4492         /* if we are writing a final moov, then we need to increase our chunk
4493          * offsets to include the moof/mdat headers that were just written so
4494          * so that they are correctly skipped over.
4495          */
4496         buf_size = (buf ? gst_buffer_get_size (buf) : 0);
4497         chunk_increase = moof_size + 16;
4498         /* we need to undo the addition to qtmux->current_chunk_size of this
4499          * buffer performed in gst_qt_mux_register_buffer_in_chunk() */
4500         chunk_increase += qtmux->current_chunk_size - buf_size;
4501         GST_LOG_OBJECT (qtmux, "We think we have currently written %"
4502             G_GUINT64_FORMAT " including a moof of %" G_GUINT64_FORMAT
4503             " mangling this buffer's chunk offset from %" G_GUINT64_FORMAT
4504             " to %" G_GUINT64_FORMAT, qtmux->header_size, moof_size,
4505             chunk_offset, chunk_offset + chunk_increase);
4506         chunk_offset += chunk_increase;
4507         /* this is the offset for the next chunk */
4508         qtmux->current_chunk_offset += chunk_increase;
4509         qtmux->current_chunk_size = buf_size;
4510         GST_LOG_OBJECT (qtmux, "change next chunk offset to %" G_GUINT64_FORMAT
4511             " and size to %" G_GUINT64_FORMAT, qtmux->current_chunk_offset,
4512             qtmux->current_chunk_size);
4513 
4514         /* if we are are generating a moof, it is for all streams */
4515         gst_element_foreach_sink_pad (GST_ELEMENT (qtmux),
4516             gst_qtmux_pad_update_fragment_duration, NULL);
4517       }
4518     } else {
4519       /* not moov-related. writes out moof then mdat for a single stream only */
4520       AtomMOOF *moof;
4521       guint64 size = 0, offset = 0;
4522       guint8 *data = NULL;
4523       GstBuffer *moof_buffer;
4524       guint i, total_size;
4525       AtomTRUN *first_trun;
4526 
4527       total_size = 0;
4528       for (i = 0; i < atom_array_get_len (&pad->fragment_buffers); i++) {
4529         total_size +=
4530             gst_buffer_get_size (atom_array_index (&pad->fragment_buffers, i));
4531       }
4532 
4533       moof = atom_moof_new (qtmux->context, qtmux->fragment_sequence);
4534       /* takes ownership */
4535       atom_moof_add_traf (moof, pad->traf);
4536       /* write the offset into the first 'trun'.  All other truns are assumed
4537        * to follow on from this trun.  Skip over the mdat header (+12) */
4538       atom_moof_copy_data (moof, &data, &size, &offset);
4539       first_trun = (AtomTRUN *) pad->traf->truns->data;
4540       atom_trun_set_offset (first_trun, offset + 12);
4541       pad->traf = NULL;
4542       size = offset = 0;
4543       atom_moof_copy_data (moof, &data, &size, &offset);
4544       moof_buffer = _gst_buffer_new_take_data (data, offset);
4545 
4546       atom_moof_free (moof);
4547 
4548       /* now we know where moof ends up, update offset in tfra */
4549       if (pad->tfra)
4550         atom_tfra_update_offset (pad->tfra, qtmux->header_size);
4551 
4552       GST_LOG_OBJECT (qtmux, "writing moof size %" G_GSIZE_FORMAT,
4553           gst_buffer_get_size (moof_buffer));
4554       ret =
4555           gst_qt_mux_send_buffer (qtmux, moof_buffer, &qtmux->header_size,
4556           FALSE);
4557       if (ret != GST_FLOW_OK)
4558         goto moof_send_error;
4559 
4560       GST_LOG_OBJECT (qtmux, "writing %d buffers, total_size %d",
4561           atom_array_get_len (&pad->fragment_buffers), total_size);
4562 
4563       ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, total_size,
4564           FALSE, FALSE);
4565       if (ret != GST_FLOW_OK)
4566         goto mdat_header_send_error;
4567 
4568       for (index = 0; index < atom_array_get_len (&pad->fragment_buffers);
4569           index++) {
4570         GST_DEBUG_OBJECT (qtmux, "sending fragment %p",
4571             atom_array_index (&pad->fragment_buffers, index));
4572         ret =
4573             gst_qt_mux_send_buffer (qtmux,
4574             atom_array_index (&pad->fragment_buffers, index),
4575             &qtmux->header_size, FALSE);
4576         if (ret != GST_FLOW_OK)
4577           goto fragment_buf_send_error;
4578       }
4579 
4580     }
4581     atom_array_clear (&pad->fragment_buffers);
4582     qtmux->fragment_sequence++;
4583     force = FALSE;
4584   }
4585 
4586 init:
4587   if (qtmux->fragment_mode == GST_QT_MUX_FRAGMENT_FIRST_MOOV_THEN_FINALISE
4588       && qtmux->fragment_sequence == 0) {
4589     atom_trak_add_samples (pad->trak, nsamples, (gint32) delta, size,
4590         chunk_offset, sync, pts_offset);
4591 
4592     ret = gst_qt_mux_send_buffer (qtmux, buf, &qtmux->mdat_size, TRUE);
4593     if (ret != GST_FLOW_OK)
4594       return ret;
4595     buf = NULL;
4596 
4597     if (G_UNLIKELY (force))
4598       goto flush;
4599 
4600     if (!pad->traf) {
4601       pad->traf = atom_traf_new (qtmux->context, atom_trak_get_id (pad->trak));
4602       pad->fragment_duration = gst_util_uint64_scale (qtmux->fragment_duration,
4603           atom_trak_get_timescale (pad->trak), 1000);
4604     }
4605     pad->fragment_duration -= delta;
4606 
4607     return ret;
4608   } else if (G_UNLIKELY (!pad->traf)) {
4609     GstClockTime first_dts = 0, current_dts;
4610     gint64 first_qt_dts;
4611     GST_LOG_OBJECT (pad, "setting up new fragment");
4612     pad->traf = atom_traf_new (qtmux->context, atom_trak_get_id (pad->trak));
4613     atom_array_init (&pad->fragment_buffers, 512);
4614     pad->fragment_duration = gst_util_uint64_scale (qtmux->fragment_duration,
4615         atom_trak_get_timescale (pad->trak), 1000);
4616 
4617     if (G_UNLIKELY (qtmux->mfra && !pad->tfra)) {
4618       pad->tfra = atom_tfra_new (qtmux->context, atom_trak_get_id (pad->trak));
4619       atom_mfra_add_tfra (qtmux->mfra, pad->tfra);
4620     }
4621     if (GST_CLOCK_TIME_IS_VALID (pad->first_dts))
4622       first_dts = pad->first_dts;
4623 
4624     current_dts =
4625         gst_util_uint64_scale (dts, GST_SECOND,
4626         atom_trak_get_timescale (pad->trak));
4627     first_qt_dts =
4628         gst_util_uint64_scale (first_dts, atom_trak_get_timescale (pad->trak),
4629         GST_SECOND);
4630     GST_DEBUG_OBJECT (pad, "calculating base decode time with first dts %"
4631         G_GINT64_FORMAT " (%" GST_TIME_FORMAT ") and current dts %"
4632         G_GINT64_FORMAT " (%" GST_TIME_FORMAT ") of %" G_GINT64_FORMAT " (%"
4633         GST_STIME_FORMAT ")", first_qt_dts, GST_TIME_ARGS (first_dts), dts,
4634         GST_TIME_ARGS (current_dts), dts - first_qt_dts,
4635         GST_STIME_ARGS (current_dts - first_dts));
4636     atom_traf_set_base_decode_time (pad->traf, dts - first_qt_dts);
4637   }
4638 
4639   if (qtmux->fragment_mode == GST_QT_MUX_FRAGMENT_FIRST_MOOV_THEN_FINALISE) {
4640     if (qtmux->fragment_sequence > 0 && !force) {
4641       if (qtmux->moof_mdat_pos == 0) {
4642         /* send temporary mdat */
4643         qtmux->moof_mdat_pos = qtmux->header_size;
4644         ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0,
4645             TRUE, FALSE);
4646         if (ret != GST_FLOW_OK)
4647           goto mdat_header_send_error;
4648       }
4649 
4650       if (buf) {
4651         atom_trak_add_samples (pad->trak, nsamples, (gint32) delta, size,
4652             chunk_offset, sync, pts_offset);
4653         atom_traf_add_samples (pad->traf, nsamples, delta, size,
4654             qtmux->header_size - qtmux->moof_mdat_pos, sync, pts_offset,
4655             pad->sync && sync);
4656 
4657         ret = gst_qt_mux_send_buffer (qtmux, buf, &qtmux->header_size, TRUE);
4658         if (ret != GST_FLOW_OK)
4659           return ret;
4660         buf = NULL;
4661       }
4662     }
4663   } else {
4664     /* add buffer and metadata */
4665     atom_traf_add_samples (pad->traf, nsamples, delta, size, 0, sync,
4666         pts_offset, pad->sync && sync);
4667     GST_LOG_OBJECT (qtmux, "adding buffer %p to fragments", buf);
4668     atom_array_append (&pad->fragment_buffers, g_steal_pointer (&buf), 256);
4669   }
4670   pad->fragment_duration -= delta;
4671 
4672   if (pad->tfra) {
4673     guint32 sn = atom_traf_get_sample_num (pad->traf);
4674 
4675     if ((sync && pad->sync) || (sn == 1 && !pad->sync))
4676       atom_tfra_add_entry (pad->tfra, dts, sn);
4677   }
4678 
4679   if (G_UNLIKELY (force))
4680     goto flush;
4681 
4682   return ret;
4683 
4684 moof_send_error:
4685   {
4686     guint i;
4687 
4688     GST_ERROR_OBJECT (qtmux, "Failed to send moof buffer");
4689     for (i = 0; i < atom_array_get_len (&pad->fragment_buffers); i++)
4690       gst_buffer_unref (atom_array_index (&pad->fragment_buffers, i));
4691     atom_array_clear (&pad->fragment_buffers);
4692     gst_clear_buffer (&buf);
4693 
4694     return ret;
4695   }
4696 
4697 mdat_header_send_error:
4698   {
4699     guint i;
4700 
4701     GST_ERROR_OBJECT (qtmux, "Failed to send mdat header");
4702     for (i = 0; i < atom_array_get_len (&pad->fragment_buffers); i++)
4703       gst_buffer_unref (atom_array_index (&pad->fragment_buffers, i));
4704     atom_array_clear (&pad->fragment_buffers);
4705     gst_clear_buffer (&buf);
4706 
4707     return ret;
4708   }
4709 
4710 fragment_buf_send_error:
4711   {
4712     guint i;
4713 
4714     GST_ERROR_OBJECT (qtmux, "Failed to send fragment");
4715     for (i = index + 1; i < atom_array_get_len (&pad->fragment_buffers); i++) {
4716       gst_buffer_unref (atom_array_index (&pad->fragment_buffers, i));
4717     }
4718     atom_array_clear (&pad->fragment_buffers);
4719     gst_clear_buffer (&buf);
4720 
4721     return ret;
4722   }
4723 }
4724 
4725 /* Here's the clever bit of robust recording: Updating the moov
4726  * header is done using a ping-pong scheme inside 2 blocks of size
4727  * 'reserved_moov_size' at the start of the file, in such a way that the
4728  * file on-disk is always valid if interrupted.
4729  * Inside the reserved space, we have 2 pairs of free + moov atoms
4730  * (in that order), free-A + moov-A @ offset 0 and free-B + moov-B at
4731  * at offset "reserved_moov_size".
4732  *
4733  * 1. Free-A has 0 size payload, moov-A immediately after is
4734  *    active/current, and is padded with an internal Free atom to
4735  *    end at reserved_space/2. Free-B is at reserved_space/2, sized
4736  *    to cover the remaining free space (including moov-B).
4737  * 2. We write moov-B (which is invisible inside free-B), and pad it to
4738  *    end at the end of free space. Then, we update free-A to size
4739  *    reserved_space/2 + sizeof(free-B), which hides moov-A and the
4740  *    free-B header, and makes moov-B active.
4741  * 3. Rewrite moov-A inside free-A, with padding out to free-B.
4742  *    Change the size of free-A to make moov-A active again.
4743  * 4. Rinse and repeat.
4744  *
4745  */
4746 static GstFlowReturn
gst_qt_mux_robust_recording_rewrite_moov(GstQTMux * qtmux)4747 gst_qt_mux_robust_recording_rewrite_moov (GstQTMux * qtmux)
4748 {
4749   GstFlowReturn ret;
4750   guint64 freeA_offset;
4751   guint32 new_freeA_size;
4752   guint64 new_moov_offset;
4753 
4754   /* Update moov info, then seek and rewrite the MOOV atom */
4755   gst_qt_mux_update_global_statistics (qtmux);
4756   gst_qt_mux_configure_moov (qtmux);
4757 
4758   gst_qt_mux_update_edit_lists (qtmux);
4759 
4760   /* tags into file metadata */
4761   gst_qt_mux_setup_metadata (qtmux);
4762 
4763   /* chunks position is set relative to the first byte of the
4764    * MDAT atom payload. Set the overall offset into the file */
4765   atom_moov_chunks_set_offset (qtmux->moov, qtmux->header_size);
4766 
4767   /* Calculate which moov to rewrite. qtmux->moov_pos points to
4768    * the start of the free-A header */
4769   freeA_offset = qtmux->moov_pos;
4770   if (qtmux->reserved_moov_first_active) {
4771     GST_DEBUG_OBJECT (qtmux, "Updating pong moov header");
4772     /* After this, freeA will include itself, moovA, plus the freeB
4773      * header */
4774     new_freeA_size = qtmux->reserved_moov_size + 16;
4775   } else {
4776     GST_DEBUG_OBJECT (qtmux, "Updating ping moov header");
4777     new_freeA_size = 8;
4778   }
4779   /* the moov we update is after free-A, calculate its offset */
4780   new_moov_offset = freeA_offset + new_freeA_size;
4781 
4782   /* Swap ping-pong cadence marker */
4783   qtmux->reserved_moov_first_active = !qtmux->reserved_moov_first_active;
4784 
4785   /* seek and rewrite the MOOV atom */
4786   gst_qt_mux_seek_to (qtmux, new_moov_offset);
4787 
4788   ret =
4789       gst_qt_mux_send_moov (qtmux, NULL, qtmux->reserved_moov_size, FALSE,
4790       TRUE);
4791   if (ret != GST_FLOW_OK)
4792     return ret;
4793 
4794   /* Update the estimated recording space remaining, based on amount used so
4795    * far and duration muxed so far */
4796   if (qtmux->last_moov_size > qtmux->base_moov_size && qtmux->last_dts > 0) {
4797     GstClockTime remain;
4798     GstClockTime time_muxed = qtmux->last_dts;
4799 
4800     remain =
4801         gst_util_uint64_scale (qtmux->reserved_moov_size -
4802         qtmux->last_moov_size, time_muxed,
4803         qtmux->last_moov_size - qtmux->base_moov_size);
4804     /* Always under-estimate slightly, so users
4805      * have time to stop muxing before we run out */
4806     if (remain < GST_SECOND / 2)
4807       remain = 0;
4808     else
4809       remain -= GST_SECOND / 2;
4810 
4811     GST_INFO_OBJECT (qtmux,
4812         "Reserved %u header bytes. Used %u in %" GST_TIME_FORMAT
4813         ". Remaining now %u or approx %" G_GUINT64_FORMAT " ns\n",
4814         qtmux->reserved_moov_size, qtmux->last_moov_size,
4815         GST_TIME_ARGS (qtmux->last_dts),
4816         qtmux->reserved_moov_size - qtmux->last_moov_size, remain);
4817 
4818     GST_OBJECT_LOCK (qtmux);
4819     qtmux->reserved_duration_remaining = remain;
4820     qtmux->muxed_since_last_update = 0;
4821     GST_DEBUG_OBJECT (qtmux, "reserved remaining duration now %"
4822         G_GUINT64_FORMAT, qtmux->reserved_duration_remaining);
4823     GST_OBJECT_UNLOCK (qtmux);
4824   }
4825 
4826 
4827   /* Now update the moov-A size. Don't pass offset, since we don't need
4828    * send_free_atom() to seek for us - all our callers seek back to
4829    * where they need after this, or they don't need it */
4830   gst_qt_mux_seek_to (qtmux, freeA_offset);
4831 
4832   ret = gst_qt_mux_send_free_atom (qtmux, NULL, new_freeA_size, TRUE);
4833 
4834   return ret;
4835 }
4836 
4837 static GstFlowReturn
gst_qt_mux_robust_recording_update(GstQTMux * qtmux,GstClockTime position)4838 gst_qt_mux_robust_recording_update (GstQTMux * qtmux, GstClockTime position)
4839 {
4840   GstFlowReturn flow_ret;
4841 
4842   guint64 mdat_offset = qtmux->mdat_pos + 16 + qtmux->mdat_size;
4843 
4844   GST_OBJECT_LOCK (qtmux);
4845 
4846   /* Update the offset of how much we've muxed, so the
4847    * report of remaining space keeps counting down */
4848   if (position > qtmux->last_moov_update &&
4849       position - qtmux->last_moov_update > qtmux->muxed_since_last_update) {
4850     GST_LOG_OBJECT (qtmux,
4851         "Muxed time %" G_GUINT64_FORMAT " since last moov update",
4852         qtmux->muxed_since_last_update);
4853     qtmux->muxed_since_last_update = position - qtmux->last_moov_update;
4854   }
4855 
4856   /* Next, check if we're supposed to send periodic moov updates downstream */
4857   if (qtmux->reserved_moov_update_period == GST_CLOCK_TIME_NONE) {
4858     GST_OBJECT_UNLOCK (qtmux);
4859     return GST_FLOW_OK;
4860   }
4861 
4862   /* Update if position is > the threshold or there's been no update yet */
4863   if (qtmux->last_moov_update != GST_CLOCK_TIME_NONE &&
4864       (position <= qtmux->last_moov_update ||
4865           (position - qtmux->last_moov_update) <
4866           qtmux->reserved_moov_update_period)) {
4867     GST_OBJECT_UNLOCK (qtmux);
4868     return GST_FLOW_OK;         /* No update needed yet */
4869   }
4870 
4871   qtmux->last_moov_update = position;
4872   GST_OBJECT_UNLOCK (qtmux);
4873 
4874   GST_DEBUG_OBJECT (qtmux, "Update moov atom, position %" GST_TIME_FORMAT
4875       " mdat starts @ %" G_GUINT64_FORMAT " we were a %" G_GUINT64_FORMAT,
4876       GST_TIME_ARGS (position), qtmux->mdat_pos, mdat_offset);
4877 
4878   flow_ret = gst_qt_mux_robust_recording_rewrite_moov (qtmux);
4879   if (G_UNLIKELY (flow_ret != GST_FLOW_OK))
4880     return flow_ret;
4881 
4882   /* Seek back to previous position */
4883   gst_qt_mux_seek_to (qtmux, mdat_offset);
4884 
4885   return flow_ret;
4886 }
4887 
4888 static GstFlowReturn
gst_qt_mux_register_and_push_sample(GstQTMux * qtmux,GstQTMuxPad * pad,GstBuffer * buffer,gboolean is_last_buffer,guint nsamples,gint64 last_dts,gint64 scaled_duration,guint sample_size,guint64 chunk_offset,gboolean sync,gboolean do_pts,gint64 pts_offset)4889 gst_qt_mux_register_and_push_sample (GstQTMux * qtmux, GstQTMuxPad * pad,
4890     GstBuffer * buffer, gboolean is_last_buffer, guint nsamples,
4891     gint64 last_dts, gint64 scaled_duration, guint sample_size,
4892     guint64 chunk_offset, gboolean sync, gboolean do_pts, gint64 pts_offset)
4893 {
4894   GstFlowReturn ret = GST_FLOW_OK;
4895 
4896   /* note that a new chunk is started each time (not fancy but works) */
4897   if (qtmux->moov_recov_file) {
4898     if (!atoms_recov_write_trak_samples (qtmux->moov_recov_file, pad->trak,
4899             nsamples, (gint32) scaled_duration, sample_size, chunk_offset, sync,
4900             do_pts, pts_offset)) {
4901       GST_WARNING_OBJECT (qtmux, "Failed to write sample information to "
4902           "recovery file, disabling recovery");
4903       fclose (qtmux->moov_recov_file);
4904       qtmux->moov_recov_file = NULL;
4905     }
4906   }
4907 
4908   switch (qtmux->mux_mode) {
4909     case GST_QT_MUX_MODE_ROBUST_RECORDING_PREFILL:{
4910       const TrakBufferEntryInfo *sample_entry;
4911       guint64 block_idx = prefill_get_block_index (qtmux, pad);
4912 
4913       if (block_idx >= pad->samples->len) {
4914         GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
4915             ("Unexpected sample %" G_GUINT64_FORMAT ", expected up to %u",
4916                 block_idx, pad->samples->len));
4917         gst_buffer_unref (buffer);
4918         return GST_FLOW_ERROR;
4919       }
4920 
4921       /* Check if all values are as expected */
4922       sample_entry =
4923           &g_array_index (pad->samples, TrakBufferEntryInfo, block_idx);
4924 
4925       /* Allow +/- 1 difference for the scaled_duration to allow
4926        * for some rounding errors
4927        */
4928       if (sample_entry->nsamples != nsamples
4929           || ABSDIFF (sample_entry->delta, scaled_duration) > 1
4930           || sample_entry->size != sample_size
4931           || sample_entry->chunk_offset != chunk_offset
4932           || sample_entry->pts_offset != pts_offset
4933           || sample_entry->sync != sync) {
4934         GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
4935             ("Unexpected values in sample %" G_GUINT64_FORMAT,
4936                 pad->sample_offset + 1));
4937         GST_ERROR_OBJECT (qtmux, "Expected: samples %u, delta %u, size %u, "
4938             "chunk offset %" G_GUINT64_FORMAT ", "
4939             "pts offset %" G_GUINT64_FORMAT ", sync %d",
4940             sample_entry->nsamples,
4941             sample_entry->delta,
4942             sample_entry->size,
4943             sample_entry->chunk_offset,
4944             sample_entry->pts_offset, sample_entry->sync);
4945         GST_ERROR_OBJECT (qtmux, "Got: samples %u, delta %u, size %u, "
4946             "chunk offset %" G_GUINT64_FORMAT ", "
4947             "pts offset %" G_GUINT64_FORMAT ", sync %d",
4948             nsamples,
4949             (guint) scaled_duration,
4950             sample_size, chunk_offset, pts_offset, sync);
4951 
4952         gst_buffer_unref (buffer);
4953         return GST_FLOW_ERROR;
4954       }
4955 
4956       ret = gst_qt_mux_send_buffer (qtmux, buffer, &qtmux->mdat_size, TRUE);
4957       break;
4958     }
4959     case GST_QT_MUX_MODE_MOOV_AT_END:
4960     case GST_QT_MUX_MODE_FAST_START:
4961     case GST_QT_MUX_MODE_ROBUST_RECORDING:
4962       atom_trak_add_samples (pad->trak, nsamples, (gint32) scaled_duration,
4963           sample_size, chunk_offset, sync, pts_offset);
4964       if (qtmux->mux_mode == GST_QT_MUX_MODE_MOOV_AT_END
4965           && !qtmux->downstream_seekable) {
4966         qtmux->output_buffers = g_list_append (qtmux->output_buffers, buffer);
4967         ret = GST_FLOW_OK;
4968       } else {
4969         ret = gst_qt_mux_send_buffer (qtmux, buffer, &qtmux->mdat_size, TRUE);
4970       }
4971       /* Check if it's time to re-write the headers in robust-recording mode */
4972       if (ret == GST_FLOW_OK
4973           && qtmux->mux_mode == GST_QT_MUX_MODE_ROBUST_RECORDING)
4974         ret = gst_qt_mux_robust_recording_update (qtmux, pad->total_duration);
4975       break;
4976     case GST_QT_MUX_MODE_FRAGMENTED:
4977       /* ensure that always sync samples are marked as such */
4978       ret = gst_qt_mux_pad_fragment_add_buffer (qtmux, pad, buffer,
4979           is_last_buffer, nsamples, last_dts, (gint32) scaled_duration,
4980           sample_size, chunk_offset, !pad->sync || sync, pts_offset);
4981       break;
4982   }
4983 
4984   return ret;
4985 }
4986 
4987 static void
gst_qt_mux_register_buffer_in_chunk(GstQTMux * qtmux,GstQTMuxPad * pad,guint buffer_size,GstClockTime duration)4988 gst_qt_mux_register_buffer_in_chunk (GstQTMux * qtmux, GstQTMuxPad * pad,
4989     guint buffer_size, GstClockTime duration)
4990 {
4991   /* not that much happens here,
4992    * but updating any of this very likely needs to happen all in sync,
4993    * unless there is a very good reason not to */
4994 
4995   /* for computing the avg bitrate */
4996   pad->total_bytes += buffer_size;
4997   pad->total_duration += duration;
4998   /* for keeping track of where we are in chunk;
4999    * ensures that data really is located as recorded in atoms */
5000 
5001   qtmux->current_chunk_size += buffer_size;
5002   qtmux->current_chunk_duration += duration;
5003 }
5004 
5005 static GstFlowReturn
gst_qt_mux_check_and_update_timecode(GstQTMux * qtmux,GstQTMuxPad * pad,GstBuffer * buf,GstFlowReturn ret)5006 gst_qt_mux_check_and_update_timecode (GstQTMux * qtmux, GstQTMuxPad * pad,
5007     GstBuffer * buf, GstFlowReturn ret)
5008 {
5009   GstVideoTimeCodeMeta *tc_meta;
5010   GstVideoTimeCode *tc;
5011   GstBuffer *tc_buf;
5012   gsize szret;
5013   guint32 frames_since_daily_jam;
5014   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
5015 
5016   if (!pad->trak->is_video)
5017     return ret;
5018 
5019   if (qtmux_klass->format != GST_QT_MUX_FORMAT_QT &&
5020       !qtmux->force_create_timecode_trak)
5021     return ret;
5022 
5023   if (buf == NULL || (pad->tc_trak != NULL && pad->tc_pos == -1))
5024     return ret;
5025 
5026   tc_meta = gst_buffer_get_video_time_code_meta (buf);
5027   if (!tc_meta)
5028     return ret;
5029 
5030   tc = &tc_meta->tc;
5031 
5032   /* This means we never got a timecode before */
5033   if (pad->first_tc == NULL) {
5034     guint64 *offset;
5035 
5036 #ifndef GST_DISABLE_GST_DEBUG
5037     gchar *tc_str = gst_video_time_code_to_string (tc);
5038     GST_DEBUG_OBJECT (qtmux, "Found first timecode %s", tc_str);
5039     g_free (tc_str);
5040 #endif
5041     g_assert (pad->tc_trak == NULL);
5042     pad->first_tc = gst_video_time_code_copy (tc);
5043 
5044     if (qtmux->mux_mode == GST_QT_MUX_MODE_FRAGMENTED
5045         && qtmux->fragment_sequence > 0) {
5046       offset = &qtmux->header_size;
5047     } else {
5048       offset = &qtmux->mdat_size;
5049     }
5050     /* If frames are out of order, the frame we're currently getting might
5051      * not be the first one. Just write a 0 timecode for now and wait
5052      * until we receive a timecode that's lower than the current one */
5053     if (pad->is_out_of_order) {
5054       pad->first_pts = GST_BUFFER_PTS (buf);
5055       frames_since_daily_jam = 0;
5056       /* Position to rewrite */
5057       pad->tc_pos = *offset;
5058     } else {
5059       frames_since_daily_jam =
5060           gst_video_time_code_frames_since_daily_jam (pad->first_tc);
5061       frames_since_daily_jam = GUINT32_TO_BE (frames_since_daily_jam);
5062     }
5063     /* Write the timecode trak now */
5064     pad->tc_trak = atom_trak_new (qtmux->context);
5065     atom_moov_add_trak (qtmux->moov, pad->tc_trak);
5066 
5067     pad->trak->tref = atom_tref_new (FOURCC_tmcd);
5068     atom_tref_add_entry (pad->trak->tref, pad->tc_trak->tkhd.track_ID);
5069 
5070     atom_trak_set_timecode_type (pad->tc_trak, qtmux->context,
5071         pad->trak->mdia.mdhd.time_info.timescale, pad->first_tc);
5072 
5073     tc_buf = gst_buffer_new_allocate (NULL, 4, NULL);
5074     szret = gst_buffer_fill (tc_buf, 0, &frames_since_daily_jam, 4);
5075     g_assert (szret == 4);
5076 
5077     atom_trak_add_samples (pad->tc_trak, 1, 1, 4, *offset, FALSE, 0);
5078 
5079     if (qtmux->mux_mode == GST_QT_MUX_MODE_MOOV_AT_END
5080         && !qtmux->downstream_seekable) {
5081       ret = gst_qtmux_push_mdat_stored_buffers (qtmux);
5082       qtmux->output_buffers = g_list_append (qtmux->output_buffers, tc_buf);
5083       ret = GST_FLOW_OK;
5084     } else {
5085       ret = gst_qt_mux_send_buffer (qtmux, tc_buf, offset, TRUE);
5086     }
5087 
5088     /* Need to reset the current chunk (of the previous pad) here because
5089      * some other data was written now above, and the pad has to start a
5090      * new chunk now */
5091     qtmux->current_chunk_offset = -1;
5092     qtmux->current_chunk_size = 0;
5093     qtmux->current_chunk_duration = 0;
5094   } else if (qtmux->mux_mode == GST_QT_MUX_MODE_ROBUST_RECORDING_PREFILL) {
5095     frames_since_daily_jam =
5096         gst_video_time_code_frames_since_daily_jam (pad->first_tc);
5097     frames_since_daily_jam = GUINT32_TO_BE (frames_since_daily_jam);
5098 
5099     tc_buf = gst_buffer_new_allocate (NULL, 4, NULL);
5100     szret = gst_buffer_fill (tc_buf, 0, &frames_since_daily_jam, 4);
5101     g_assert (szret == 4);
5102 
5103     if (qtmux->mux_mode == GST_QT_MUX_MODE_MOOV_AT_END
5104         && !qtmux->downstream_seekable) {
5105       ret = gst_qtmux_push_mdat_stored_buffers (qtmux);
5106       qtmux->output_buffers = g_list_append (qtmux->output_buffers, tc_buf);
5107       ret = GST_FLOW_OK;
5108     } else {
5109       ret = gst_qt_mux_send_buffer (qtmux, tc_buf, &qtmux->mdat_size, TRUE);
5110     }
5111     pad->tc_pos = -1;
5112 
5113     qtmux->current_chunk_offset = -1;
5114     qtmux->current_chunk_size = 0;
5115     qtmux->current_chunk_duration = 0;
5116   } else if (pad->is_out_of_order) {
5117     /* Check for a lower timecode than the one stored */
5118     g_assert (pad->tc_trak != NULL);
5119     if (GST_BUFFER_DTS (buf) <= pad->first_pts) {
5120       if (gst_video_time_code_compare (tc, pad->first_tc) == -1) {
5121         gst_video_time_code_free (pad->first_tc);
5122         pad->first_tc = gst_video_time_code_copy (tc);
5123       }
5124     } else {
5125       guint64 bk_size = qtmux->mdat_size;
5126       /* If this frame's DTS is after the first PTS received, it means
5127        * we've already received the first frame to be presented. Otherwise
5128        * the decoder would need to go back in time */
5129       gst_qt_mux_update_timecode (qtmux, pad);
5130 
5131       /* Reset writing position */
5132       gst_qt_mux_seek_to (qtmux, bk_size);
5133     }
5134   }
5135 
5136   return ret;
5137 }
5138 
5139 /*
5140  * Here we push the buffer and update the tables in the track atoms
5141  */
5142 static GstFlowReturn
gst_qt_mux_add_buffer(GstQTMux * qtmux,GstQTMuxPad * pad,GstBuffer * buf)5143 gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTMuxPad * pad, GstBuffer * buf)
5144 {
5145   GstBuffer *last_buf = NULL;
5146   GstClockTime duration;
5147   guint nsamples, sample_size;
5148   guint64 chunk_offset;
5149   gint64 last_dts, scaled_duration;
5150   gint64 pts_offset = 0;
5151   gboolean sync = FALSE;
5152   GstFlowReturn ret = GST_FLOW_OK;
5153   guint buffer_size;
5154 
5155   /* GAP event, nothing to do */
5156   if (buf && gst_buffer_get_size (buf) == 0 &&
5157       GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_GAP)) {
5158     gst_buffer_unref (buf);
5159     return GST_FLOW_OK;
5160   }
5161 
5162   if (!pad->fourcc)
5163     goto not_negotiated;
5164 
5165   /* if this pad has a prepare function, call it */
5166   if (pad->prepare_buf_func != NULL) {
5167     GstBuffer *new_buf;
5168 
5169     new_buf = pad->prepare_buf_func (pad, buf, qtmux);
5170     if (buf && !new_buf)
5171       return GST_FLOW_OK;
5172     buf = new_buf;
5173   }
5174 
5175   ret = gst_qt_mux_check_and_update_timecode (qtmux, pad, buf, ret);
5176   if (ret != GST_FLOW_OK) {
5177     if (buf)
5178       gst_buffer_unref (buf);
5179     return ret;
5180   }
5181 
5182   last_buf = pad->last_buf;
5183   pad->last_buf = buf;
5184 
5185   if (last_buf == NULL) {
5186 #ifndef GST_DISABLE_GST_DEBUG
5187     if (buf == NULL) {
5188       GST_DEBUG_OBJECT (qtmux, "Pad %s has no previous buffer stored and "
5189           "received NULL buffer, doing nothing", GST_PAD_NAME (pad));
5190     } else {
5191       GST_LOG_OBJECT (qtmux,
5192           "Pad %s has no previous buffer stored, storing now",
5193           GST_PAD_NAME (pad));
5194     }
5195 #endif
5196     goto exit;
5197   }
5198 
5199   if (!GST_BUFFER_PTS_IS_VALID (last_buf))
5200     goto no_pts;
5201 
5202   /* if this is the first buffer, store the timestamp */
5203   if (G_UNLIKELY (pad->first_ts == GST_CLOCK_TIME_NONE)) {
5204     if (GST_BUFFER_PTS_IS_VALID (last_buf)) {
5205       pad->first_ts = GST_BUFFER_PTS (last_buf);
5206     } else if (GST_BUFFER_DTS_IS_VALID (last_buf)) {
5207       pad->first_ts = GST_BUFFER_DTS (last_buf);
5208     }
5209 
5210     if (GST_BUFFER_DTS_IS_VALID (last_buf)) {
5211       pad->first_dts = pad->last_dts = GST_BUFFER_DTS (last_buf);
5212     } else if (GST_BUFFER_PTS_IS_VALID (last_buf)) {
5213       pad->first_dts = pad->last_dts = GST_BUFFER_PTS (last_buf);
5214     }
5215 
5216     if (GST_CLOCK_TIME_IS_VALID (pad->first_ts)) {
5217       GST_DEBUG ("setting first_ts to %" G_GUINT64_FORMAT, pad->first_ts);
5218     } else {
5219       GST_WARNING_OBJECT (qtmux, "First buffer for pad %s has no timestamp, "
5220           "using 0 as first timestamp", GST_PAD_NAME (pad));
5221       pad->first_ts = pad->first_dts = 0;
5222     }
5223     GST_DEBUG_OBJECT (qtmux, "Stored first timestamp for pad %s %"
5224         GST_TIME_FORMAT, GST_PAD_NAME (pad), GST_TIME_ARGS (pad->first_ts));
5225   }
5226 
5227   if (buf && GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DTS (buf)) &&
5228       GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DTS (last_buf)) &&
5229       GST_BUFFER_DTS (buf) < GST_BUFFER_DTS (last_buf)) {
5230     GST_ERROR ("decreasing DTS value %" GST_TIME_FORMAT " < %" GST_TIME_FORMAT,
5231         GST_TIME_ARGS (GST_BUFFER_DTS (buf)),
5232         GST_TIME_ARGS (GST_BUFFER_DTS (last_buf)));
5233     pad->last_buf = buf = gst_buffer_make_writable (buf);
5234     GST_BUFFER_DTS (buf) = GST_BUFFER_DTS (last_buf);
5235   }
5236 
5237   buffer_size = gst_buffer_get_size (last_buf);
5238 
5239   if (qtmux->mux_mode == GST_QT_MUX_MODE_ROBUST_RECORDING_PREFILL) {
5240     guint required_buffer_size = prefill_get_sample_size (qtmux, pad);
5241     guint fill_size = required_buffer_size - buffer_size;
5242     GstMemory *mem;
5243     GstMapInfo map;
5244 
5245     if (required_buffer_size < buffer_size) {
5246       GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
5247           ("Sample size %u bigger than expected maximum %u", buffer_size,
5248               required_buffer_size));
5249       goto bail;
5250     }
5251 
5252     if (fill_size > 0) {
5253       GST_DEBUG_OBJECT (qtmux,
5254           "Padding buffer by %u bytes to reach required %u bytes", fill_size,
5255           required_buffer_size);
5256       mem = gst_allocator_alloc (NULL, fill_size, NULL);
5257       gst_memory_map (mem, &map, GST_MAP_WRITE);
5258       memset (map.data, 0, map.size);
5259       gst_memory_unmap (mem, &map);
5260       last_buf = gst_buffer_make_writable (last_buf);
5261       gst_buffer_append_memory (last_buf, mem);
5262       buffer_size = required_buffer_size;
5263     }
5264   }
5265 
5266   /* duration actually means time delta between samples, so we calculate
5267    * the duration based on the difference in DTS or PTS, falling back
5268    * to DURATION if the other two don't exist, such as with the last
5269    * sample before EOS. Or use 0 if nothing else is available,
5270    * making sure that duration doesn't go negative and wraparound. */
5271   if (GST_BUFFER_DURATION_IS_VALID (last_buf))
5272     duration = GST_BUFFER_DURATION (last_buf);
5273   else
5274     duration = 0;
5275   if (!pad->sparse) {
5276     if (buf && GST_BUFFER_DTS_IS_VALID (buf)
5277         && GST_BUFFER_DTS_IS_VALID (last_buf)) {
5278       if (GST_BUFFER_DTS (buf) >= GST_BUFFER_DTS (last_buf))
5279         duration = GST_BUFFER_DTS (buf) - GST_BUFFER_DTS (last_buf);
5280     } else if (buf && GST_BUFFER_PTS_IS_VALID (buf)
5281         && GST_BUFFER_PTS_IS_VALID (last_buf)) {
5282       if (GST_BUFFER_PTS (buf) >= GST_BUFFER_PTS (last_buf))
5283         duration = GST_BUFFER_PTS (buf) - GST_BUFFER_PTS (last_buf);
5284     }
5285     if (duration == 0 && !pad->warned_empty_duration) {
5286       GST_WARNING_OBJECT (qtmux,
5287           "Sample with zero duration on pad %" GST_PTR_FORMAT
5288           " due to missing or backward timestamps on the input stream", pad);
5289       pad->warned_empty_duration = TRUE;
5290     }
5291   }
5292 
5293   if (qtmux->current_pad != pad || qtmux->current_chunk_offset == -1) {
5294     GST_DEBUG_OBJECT (qtmux,
5295         "Switching to next chunk for pad %s:%s: offset %" G_GUINT64_FORMAT
5296         ", size %" G_GUINT64_FORMAT ", duration %" GST_TIME_FORMAT,
5297         GST_DEBUG_PAD_NAME (pad), qtmux->current_chunk_offset,
5298         qtmux->current_chunk_size,
5299         GST_TIME_ARGS (qtmux->current_chunk_duration));
5300     qtmux->current_pad = pad;
5301     if (qtmux->current_chunk_offset == -1)
5302       qtmux->current_chunk_offset = qtmux->mdat_size;
5303     else
5304       qtmux->current_chunk_offset += qtmux->current_chunk_size;
5305     qtmux->current_chunk_size = 0;
5306     qtmux->current_chunk_duration = 0;
5307   }
5308 
5309   last_dts = gst_util_uint64_scale_round (pad->last_dts,
5310       atom_trak_get_timescale (pad->trak), GST_SECOND);
5311 
5312   /* fragments only deal with 1 buffer == 1 chunk (== 1 sample) */
5313   if (pad->sample_size && (qtmux->mux_mode != GST_QT_MUX_MODE_FRAGMENTED
5314           || qtmux->fragment_mode ==
5315           GST_QT_MUX_FRAGMENT_FIRST_MOOV_THEN_FINALISE)) {
5316     GstClockTime expected_timestamp;
5317 
5318     /* Constant size packets: usually raw audio (with many samples per
5319        buffer (= chunk)), but can also be fixed-packet-size codecs like ADPCM
5320      */
5321     sample_size = pad->sample_size;
5322     if (buffer_size % sample_size != 0)
5323       goto fragmented_sample;
5324 
5325     /* note: qt raw audio storage warps it implicitly into a timewise
5326      * perfect stream, discarding buffer times.
5327      * If the difference between the current PTS and the expected one
5328      * becomes too big, we error out: there was a gap and we have no way to
5329      * represent that, causing A/V sync to be off */
5330     expected_timestamp =
5331         gst_util_uint64_scale (pad->sample_offset, GST_SECOND,
5332         atom_trak_get_timescale (pad->trak)) + pad->first_ts;
5333     if (ABSDIFF (GST_BUFFER_DTS_OR_PTS (last_buf),
5334             expected_timestamp) > qtmux->max_raw_audio_drift)
5335       goto raw_audio_timestamp_drift;
5336 
5337     if (GST_BUFFER_DURATION (last_buf) != GST_CLOCK_TIME_NONE) {
5338       nsamples = gst_util_uint64_scale_round (GST_BUFFER_DURATION (last_buf),
5339           atom_trak_get_timescale (pad->trak), GST_SECOND);
5340       duration = GST_BUFFER_DURATION (last_buf);
5341     } else {
5342       nsamples = buffer_size / sample_size;
5343       duration =
5344           gst_util_uint64_scale_round (nsamples, GST_SECOND,
5345           atom_trak_get_timescale (pad->trak));
5346     }
5347 
5348     /* timescale = samplerate */
5349     scaled_duration = 1;
5350     pad->last_dts =
5351         pad->first_dts + gst_util_uint64_scale_round (pad->sample_offset +
5352         nsamples, GST_SECOND, atom_trak_get_timescale (pad->trak));
5353   } else {
5354     nsamples = 1;
5355     sample_size = buffer_size;
5356     if (!pad->sparse && ((buf && GST_BUFFER_DTS_IS_VALID (buf))
5357             || GST_BUFFER_DTS_IS_VALID (last_buf))) {
5358       gint64 scaled_dts;
5359       if (buf && GST_BUFFER_DTS_IS_VALID (buf)) {
5360         pad->last_dts = GST_BUFFER_DTS (buf);
5361       } else {
5362         pad->last_dts = GST_BUFFER_DTS (last_buf) + duration;
5363       }
5364       if ((gint64) (pad->last_dts) < 0) {
5365         scaled_dts = -gst_util_uint64_scale_round (-pad->last_dts,
5366             atom_trak_get_timescale (pad->trak), GST_SECOND);
5367       } else {
5368         scaled_dts = gst_util_uint64_scale_round (pad->last_dts,
5369             atom_trak_get_timescale (pad->trak), GST_SECOND);
5370       }
5371       scaled_duration = scaled_dts - last_dts;
5372       last_dts = scaled_dts;
5373     } else {
5374       /* first convert intended timestamp (in GstClockTime resolution) to
5375        * trak timescale, then derive delta;
5376        * this ensures sums of (scale)delta add up to converted timestamp,
5377        * which only deviates at most 1/scale from timestamp itself */
5378       scaled_duration = gst_util_uint64_scale_round (pad->last_dts + duration,
5379           atom_trak_get_timescale (pad->trak), GST_SECOND) - last_dts;
5380       pad->last_dts += duration;
5381     }
5382   }
5383 
5384   gst_qt_mux_register_buffer_in_chunk (qtmux, pad, buffer_size, duration);
5385 
5386   chunk_offset = qtmux->current_chunk_offset;
5387 
5388   GST_LOG_OBJECT (qtmux,
5389       "Pad (%s) dts updated to %" GST_TIME_FORMAT,
5390       GST_PAD_NAME (pad), GST_TIME_ARGS (pad->last_dts));
5391   GST_LOG_OBJECT (qtmux,
5392       "Adding %d samples to track, duration: %" G_GUINT64_FORMAT
5393       " size: %" G_GUINT32_FORMAT " chunk offset: %" G_GUINT64_FORMAT,
5394       nsamples, scaled_duration, sample_size, chunk_offset);
5395 
5396   /* might be a sync sample */
5397   if (pad->sync &&
5398       !GST_BUFFER_FLAG_IS_SET (last_buf, GST_BUFFER_FLAG_DELTA_UNIT)) {
5399     GST_LOG_OBJECT (qtmux, "Adding new sync sample entry for track of pad %s",
5400         GST_PAD_NAME (pad));
5401     sync = TRUE;
5402   }
5403 
5404   if (GST_BUFFER_DTS_IS_VALID (last_buf)) {
5405     last_dts = gst_util_uint64_scale_round (GST_BUFFER_DTS (last_buf),
5406         atom_trak_get_timescale (pad->trak), GST_SECOND);
5407     pts_offset =
5408         (gint64) (gst_util_uint64_scale_round (GST_BUFFER_PTS (last_buf),
5409             atom_trak_get_timescale (pad->trak), GST_SECOND) - last_dts);
5410   } else {
5411     pts_offset = 0;
5412     last_dts = gst_util_uint64_scale_round (GST_BUFFER_PTS (last_buf),
5413         atom_trak_get_timescale (pad->trak), GST_SECOND);
5414   }
5415   GST_DEBUG ("dts: %" GST_TIME_FORMAT " pts: %" GST_TIME_FORMAT
5416       " timebase_dts: %d pts_offset: %d",
5417       GST_TIME_ARGS (GST_BUFFER_DTS (last_buf)),
5418       GST_TIME_ARGS (GST_BUFFER_PTS (last_buf)),
5419       (int) (last_dts), (int) (pts_offset));
5420 
5421   if (GST_CLOCK_TIME_IS_VALID (duration)
5422       && (qtmux->current_chunk_duration > qtmux->longest_chunk
5423           || !GST_CLOCK_TIME_IS_VALID (qtmux->longest_chunk))) {
5424     GST_DEBUG_OBJECT (qtmux,
5425         "New longest chunk found: %" GST_TIME_FORMAT ", pad %s",
5426         GST_TIME_ARGS (qtmux->current_chunk_duration), GST_PAD_NAME (pad));
5427     qtmux->longest_chunk = qtmux->current_chunk_duration;
5428   }
5429 
5430   if (qtmux->mux_mode == GST_QT_MUX_MODE_ROBUST_RECORDING_PREFILL) {
5431     const TrakBufferEntryInfo *sample_entry;
5432     guint64 block_idx = prefill_get_block_index (qtmux, pad);
5433 
5434     if (block_idx >= pad->samples->len) {
5435       GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
5436           ("Unexpected sample %" G_GUINT64_FORMAT ", expected up to %u",
5437               block_idx, pad->samples->len));
5438       goto bail;
5439     }
5440 
5441     /* Check if all values are as expected */
5442     sample_entry =
5443         &g_array_index (pad->samples, TrakBufferEntryInfo, block_idx);
5444 
5445     if (chunk_offset < sample_entry->chunk_offset) {
5446       guint fill_size = sample_entry->chunk_offset - chunk_offset;
5447       GstBuffer *fill_buf;
5448 
5449       fill_buf = gst_buffer_new_allocate (NULL, fill_size, NULL);
5450       gst_buffer_memset (fill_buf, 0, 0, fill_size);
5451 
5452       ret = gst_qt_mux_send_buffer (qtmux, fill_buf, &qtmux->mdat_size, TRUE);
5453       if (ret != GST_FLOW_OK)
5454         goto bail;
5455       qtmux->current_chunk_offset = chunk_offset = sample_entry->chunk_offset;
5456       qtmux->current_chunk_size = buffer_size;
5457       qtmux->current_chunk_duration = duration;
5458     } else if (chunk_offset != sample_entry->chunk_offset) {
5459       GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
5460           ("Unexpected chunk offset %" G_GUINT64_FORMAT ", expected up to %"
5461               G_GUINT64_FORMAT, chunk_offset, sample_entry->chunk_offset));
5462       goto bail;
5463     }
5464   }
5465 
5466   /* now we go and register this buffer/sample all over */
5467   pad->flow_status = gst_qt_mux_register_and_push_sample (qtmux, pad, last_buf,
5468       buf == NULL, nsamples, last_dts, scaled_duration, sample_size,
5469       chunk_offset, sync, TRUE, pts_offset);
5470   if (pad->flow_status != GST_FLOW_OK)
5471     goto sample_error;
5472 
5473   pad->sample_offset += nsamples;
5474 
5475   /* if this is sparse and we have a next buffer, check if there is any gap
5476    * between them to insert an empty sample */
5477   if (pad->sparse && buf) {
5478     if (pad->create_empty_buffer) {
5479       GstBuffer *empty_buf;
5480       gint64 empty_duration =
5481           GST_BUFFER_PTS (buf) - (GST_BUFFER_PTS (last_buf) + duration);
5482       gint64 empty_duration_scaled;
5483       guint empty_size;
5484 
5485       empty_buf = pad->create_empty_buffer (pad, empty_duration);
5486 
5487       pad->last_dts = GST_BUFFER_PTS (buf);
5488       empty_duration_scaled = gst_util_uint64_scale_round (pad->last_dts,
5489           atom_trak_get_timescale (pad->trak), GST_SECOND)
5490           - (last_dts + scaled_duration);
5491       empty_size = gst_buffer_get_size (empty_buf);
5492 
5493       gst_qt_mux_register_buffer_in_chunk (qtmux, pad, empty_size,
5494           empty_duration);
5495 
5496       ret =
5497           gst_qt_mux_register_and_push_sample (qtmux, pad, empty_buf, FALSE, 1,
5498           last_dts + scaled_duration, empty_duration_scaled,
5499           empty_size, chunk_offset, sync, TRUE, 0);
5500     } else if (pad->fourcc != FOURCC_c608 && pad->fourcc != FOURCC_c708) {
5501       /* This assert is kept here to make sure implementors of new
5502        * sparse input format decide whether there needs to be special
5503        * gap handling or not */
5504       g_assert_not_reached ();
5505       GST_WARNING_OBJECT (qtmux,
5506           "no empty buffer creation function found for pad %s",
5507           GST_PAD_NAME (pad));
5508     }
5509   }
5510 
5511 exit:
5512 
5513   return ret;
5514 
5515   /* ERRORS */
5516 bail:
5517   {
5518     gst_buffer_unref (last_buf);
5519     return GST_FLOW_ERROR;
5520   }
5521 fragmented_sample:
5522   {
5523     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
5524         ("Audio buffer contains fragmented sample."));
5525     goto bail;
5526   }
5527 raw_audio_timestamp_drift:
5528   {
5529     /* TODO: Could in theory be implemented with edit lists */
5530     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
5531         ("Audio stream timestamps are drifting (got %" GST_TIME_FORMAT
5532             ", expected %" GST_TIME_FORMAT "). This is not supported yet!",
5533             GST_TIME_ARGS (GST_BUFFER_DTS_OR_PTS (last_buf)),
5534             GST_TIME_ARGS (gst_util_uint64_scale (pad->sample_offset,
5535                     GST_SECOND,
5536                     atom_trak_get_timescale (pad->trak)) + pad->first_ts)));
5537     goto bail;
5538   }
5539 no_pts:
5540   {
5541     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL), ("Buffer has no PTS."));
5542     goto bail;
5543   }
5544 not_negotiated:
5545   {
5546     GST_ELEMENT_ERROR (qtmux, CORE, NEGOTIATION, (NULL),
5547         ("format wasn't negotiated before buffer flow on pad %s",
5548             GST_PAD_NAME (pad)));
5549     if (buf)
5550       gst_buffer_unref (buf);
5551     return GST_FLOW_NOT_NEGOTIATED;
5552   }
5553 sample_error:
5554   {
5555     /* Only post an error message for actual errors that are not flushing */
5556     if (pad->flow_status < GST_FLOW_OK && pad->flow_status != GST_FLOW_FLUSHING)
5557       GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
5558           ("Failed to push sample."));
5559     return pad->flow_status;
5560   }
5561 }
5562 
5563 /*
5564  * DTS running time can be negative. There is no way to represent that in
5565  * MP4 however, thus we need to offset DTS so that it starts from 0.
5566  */
5567 static void
gst_qt_pad_adjust_buffer_dts(GstQTMux * qtmux,GstQTMuxPad * pad,GstBuffer ** buf)5568 gst_qt_pad_adjust_buffer_dts (GstQTMux * qtmux, GstQTMuxPad * pad,
5569     GstBuffer ** buf)
5570 {
5571   GstClockTime pts;
5572   gint64 dts;
5573 
5574   pts = GST_BUFFER_PTS (*buf);
5575   dts = pad->dts;
5576 
5577   GST_LOG_OBJECT (qtmux, "selected pad %s with PTS %" GST_TIME_FORMAT
5578       " and DTS %" GST_STIME_FORMAT, GST_PAD_NAME (pad),
5579       GST_TIME_ARGS (pts), GST_STIME_ARGS (dts));
5580 
5581   if (!GST_CLOCK_TIME_IS_VALID (pad->dts_adjustment)) {
5582     if (GST_CLOCK_STIME_IS_VALID (dts) && dts < 0)
5583       pad->dts_adjustment = -dts;
5584     else
5585       pad->dts_adjustment = 0;
5586   }
5587 
5588   if (pad->dts_adjustment > 0) {
5589     *buf = gst_buffer_make_writable (*buf);
5590 
5591     dts += pad->dts_adjustment;
5592 
5593     if (GST_CLOCK_TIME_IS_VALID (pts))
5594       pts += pad->dts_adjustment;
5595 
5596     if (GST_CLOCK_STIME_IS_VALID (dts) && dts < 0) {
5597       GST_WARNING_OBJECT (pad, "Decreasing DTS.");
5598       dts = 0;
5599     }
5600 
5601     if (pts < dts) {
5602       GST_WARNING_OBJECT (pad, "DTS is bigger then PTS");
5603       pts = dts;
5604     }
5605 
5606     GST_BUFFER_PTS (*buf) = pts;
5607     GST_BUFFER_DTS (*buf) = dts;
5608 
5609     GST_LOG_OBJECT (qtmux, "time adjusted to PTS %" GST_TIME_FORMAT
5610         " and DTS %" GST_TIME_FORMAT, GST_TIME_ARGS (pts), GST_TIME_ARGS (dts));
5611   }
5612 }
5613 
5614 static GstQTMuxPad *
find_best_pad(GstQTMux * qtmux)5615 find_best_pad (GstQTMux * qtmux)
5616 {
5617   GList *l;
5618   GstQTMuxPad *best_pad = NULL;
5619 
5620   if (qtmux->mux_mode == GST_QT_MUX_MODE_ROBUST_RECORDING_PREFILL) {
5621     guint64 smallest_offset = G_MAXUINT64;
5622     guint64 chunk_offset = 0;
5623 
5624     GST_OBJECT_LOCK (qtmux);
5625     for (l = GST_ELEMENT_CAST (qtmux)->sinkpads; l; l = l->next) {
5626       GstQTMuxPad *qtpad = (GstQTMuxPad *) l->data;
5627       const TrakBufferEntryInfo *sample_entry;
5628       guint64 block_idx, current_block_idx;
5629       guint64 chunk_offset_offset = 0;
5630       GstBuffer *tmp_buf =
5631           gst_aggregator_pad_peek_buffer (GST_AGGREGATOR_PAD (qtpad));
5632 
5633       /* Check for EOS pads and just skip them */
5634       if (!tmp_buf && !qtpad->last_buf && (!qtpad->raw_audio_adapter
5635               || gst_adapter_available (qtpad->raw_audio_adapter) == 0))
5636         continue;
5637       if (tmp_buf)
5638         gst_buffer_unref (tmp_buf);
5639 
5640       /* Find the exact offset where the next sample of this track is supposed
5641        * to be written at */
5642       block_idx = current_block_idx = prefill_get_block_index (qtmux, qtpad);
5643       if (!qtpad->samples || block_idx >= qtpad->samples->len) {
5644         GST_ELEMENT_ERROR (qtmux, RESOURCE, SETTINGS,
5645             ("Failed to create samples in prefill mode"), (NULL));
5646         return NULL;
5647       }
5648 
5649       sample_entry =
5650           &g_array_index (qtpad->samples, TrakBufferEntryInfo, block_idx);
5651       while (block_idx > 0) {
5652         const TrakBufferEntryInfo *tmp =
5653             &g_array_index (qtpad->samples, TrakBufferEntryInfo, block_idx - 1);
5654 
5655         if (tmp->chunk_offset != sample_entry->chunk_offset)
5656           break;
5657         chunk_offset_offset += tmp->size * tmp->nsamples;
5658         block_idx--;
5659       }
5660 
5661       /* Except for the previously selected pad being EOS we always have
5662        *  qtmux->current_chunk_offset + qtmux->current_chunk_size
5663        *    ==
5664        *  sample_entry->chunk_offset + chunk_offset_offset
5665        * for the best pad. Instead of checking that, we just return the
5666        * pad that has the smallest offset for the next to-be-written sample.
5667        */
5668       if (sample_entry->chunk_offset + chunk_offset_offset < smallest_offset) {
5669         smallest_offset = sample_entry->chunk_offset + chunk_offset_offset;
5670         best_pad = qtpad;
5671         chunk_offset = sample_entry->chunk_offset;
5672       }
5673     }
5674     GST_OBJECT_UNLOCK (qtmux);
5675 
5676     if (chunk_offset != qtmux->current_chunk_offset) {
5677       qtmux->current_pad = NULL;
5678     }
5679 
5680     return best_pad;
5681   }
5682 
5683   if (qtmux->current_pad && (qtmux->interleave_bytes != 0
5684           || qtmux->interleave_time != 0) && (qtmux->interleave_bytes == 0
5685           || qtmux->current_chunk_size <= qtmux->interleave_bytes)
5686       && (qtmux->interleave_time == 0
5687           || qtmux->current_chunk_duration <= qtmux->interleave_time)
5688       && qtmux->mux_mode != GST_QT_MUX_MODE_FRAGMENTED) {
5689     GstBuffer *tmp_buf =
5690         gst_aggregator_pad_peek_buffer (GST_AGGREGATOR_PAD
5691         (qtmux->current_pad));
5692 
5693     if (tmp_buf || qtmux->current_pad->last_buf) {
5694       best_pad = qtmux->current_pad;
5695       if (tmp_buf)
5696         gst_buffer_unref (tmp_buf);
5697       GST_DEBUG_OBJECT (qtmux, "Reusing pad %s:%s",
5698           GST_DEBUG_PAD_NAME (best_pad));
5699     }
5700   } else {
5701     gboolean push_stored = FALSE;
5702 
5703     GST_OBJECT_LOCK (qtmux);
5704     if ((GST_ELEMENT (qtmux)->sinkpads && GST_ELEMENT (qtmux)->sinkpads->next)
5705         || qtmux->force_chunks) {
5706       /* Only switch pads if we have more than one, otherwise
5707        * we can just put everything into a single chunk and save
5708        * a few bytes of offsets.
5709        *
5710        * Various applications and the Apple ProRes spec require chunking even
5711        * in case of single stream files.
5712        */
5713       if (qtmux->current_pad)
5714         GST_DEBUG_OBJECT (qtmux, "Switching from pad %s:%s",
5715             GST_DEBUG_PAD_NAME (qtmux->current_pad));
5716       best_pad = qtmux->current_pad = NULL;
5717       push_stored = TRUE;
5718     }
5719     GST_OBJECT_UNLOCK (qtmux);
5720 
5721     if (push_stored)
5722       gst_qtmux_push_mdat_stored_buffers (qtmux);
5723   }
5724 
5725   if (!best_pad) {
5726     GstClockTime best_time = GST_CLOCK_TIME_NONE;
5727 
5728     GST_OBJECT_LOCK (qtmux);
5729     for (l = GST_ELEMENT_CAST (qtmux)->sinkpads; l; l = l->next) {
5730       GstQTMuxPad *qtpad = (GstQTMuxPad *) l->data;
5731       GstBuffer *tmp_buf;
5732       GstClockTime timestamp;
5733 
5734       tmp_buf = gst_aggregator_pad_peek_buffer (GST_AGGREGATOR_PAD (qtpad));;
5735       if (!tmp_buf) {
5736         /* This one is newly EOS now, finish it for real */
5737         if (qtpad->last_buf) {
5738           timestamp = GST_BUFFER_DTS_OR_PTS (qtpad->last_buf);
5739         } else {
5740           continue;
5741         }
5742       } else {
5743         if (qtpad->last_buf)
5744           timestamp = GST_BUFFER_DTS_OR_PTS (qtpad->last_buf);
5745         else
5746           timestamp = GST_BUFFER_DTS_OR_PTS (tmp_buf);
5747       }
5748 
5749       if (best_pad == NULL ||
5750           !GST_CLOCK_TIME_IS_VALID (best_time) || timestamp < best_time) {
5751         best_pad = qtpad;
5752         best_time = timestamp;
5753       }
5754 
5755       if (tmp_buf)
5756         gst_buffer_unref (tmp_buf);
5757     }
5758     GST_OBJECT_UNLOCK (qtmux);
5759 
5760     if (best_pad) {
5761       GST_DEBUG_OBJECT (qtmux, "Choosing pad %s:%s",
5762           GST_DEBUG_PAD_NAME (best_pad));
5763     } else {
5764       GST_DEBUG_OBJECT (qtmux, "No best pad: EOS");
5765     }
5766   }
5767 
5768   return best_pad;
5769 }
5770 
5771 static gboolean
gst_qt_mux_are_all_pads_eos(GstQTMux * mux)5772 gst_qt_mux_are_all_pads_eos (GstQTMux * mux)
5773 {
5774   GList *l;
5775   gboolean ret = TRUE;
5776 
5777   GST_OBJECT_LOCK (mux);
5778   for (l = GST_ELEMENT_CAST (mux)->sinkpads; l; l = l->next) {
5779     if (!gst_aggregator_pad_is_eos (GST_AGGREGATOR_PAD (l->data))) {
5780       ret = FALSE;
5781       break;
5782     }
5783   }
5784   GST_OBJECT_UNLOCK (mux);
5785 
5786   return ret;
5787 }
5788 
5789 static GstFlowReturn
gst_qt_mux_aggregate(GstAggregator * agg,gboolean timeout)5790 gst_qt_mux_aggregate (GstAggregator * agg, gboolean timeout)
5791 {
5792   GstFlowReturn ret = GST_FLOW_OK;
5793   GstQTMux *qtmux = GST_QT_MUX_CAST (agg);
5794   GstQTMuxPad *best_pad = NULL;
5795 
5796   if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_STARTED)) {
5797     if ((ret = gst_qt_mux_start_file (qtmux)) != GST_FLOW_OK)
5798       return ret;
5799 
5800     qtmux->state = GST_QT_MUX_STATE_DATA;
5801   }
5802 
5803   if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_EOS))
5804     return GST_FLOW_EOS;
5805 
5806   best_pad = find_best_pad (qtmux);
5807 
5808   /* clipping already converted to running time */
5809   if (best_pad != NULL) {
5810     GstBuffer *buf = NULL;
5811 
5812     /* FIXME: the function should always return flow_status information, that
5813      * is supposed to be stored each time buffers (collected from the pads)
5814      * are pushed. */
5815     if (best_pad->flow_status != GST_FLOW_OK)
5816       return best_pad->flow_status;
5817 
5818     if (qtmux->mux_mode != GST_QT_MUX_MODE_ROBUST_RECORDING_PREFILL ||
5819         best_pad->raw_audio_adapter == NULL ||
5820         best_pad->raw_audio_adapter_pts == GST_CLOCK_TIME_NONE)
5821       buf = gst_aggregator_pad_pop_buffer (GST_AGGREGATOR_PAD (best_pad));
5822 
5823     g_assert (buf || best_pad->last_buf || (best_pad->raw_audio_adapter
5824             && gst_adapter_available (best_pad->raw_audio_adapter) > 0));
5825 
5826     if (buf)
5827       gst_qt_pad_adjust_buffer_dts (qtmux, best_pad, &buf);
5828 
5829     ret = gst_qt_mux_add_buffer (qtmux, best_pad, buf);
5830   } else if (gst_qt_mux_are_all_pads_eos (qtmux)) {
5831 
5832     qtmux->state = GST_QT_MUX_STATE_EOS;
5833     ret = gst_qt_mux_stop_file (qtmux);
5834     if (ret == GST_FLOW_OK) {
5835       GST_DEBUG_OBJECT (qtmux, "We are eos");
5836       ret = GST_FLOW_EOS;
5837     } else {
5838       GST_WARNING_OBJECT (qtmux, "Failed to stop file: %s",
5839           gst_flow_get_name (ret));
5840     }
5841   }
5842 
5843   return ret;
5844 }
5845 
5846 static gboolean
field_is_in(GQuark field_id,const gchar * fieldname,...)5847 field_is_in (GQuark field_id, const gchar * fieldname, ...)
5848 {
5849   va_list varargs;
5850   gchar *name = (gchar *) fieldname;
5851 
5852   va_start (varargs, fieldname);
5853   while (name) {
5854     if (field_id == g_quark_from_static_string (name)) {
5855       va_end (varargs);
5856 
5857       return TRUE;
5858     }
5859 
5860     name = va_arg (varargs, char *);
5861   }
5862   va_end (varargs);
5863 
5864   return FALSE;
5865 }
5866 
5867 static gboolean
check_field(GQuark field_id,const GValue * value,gpointer user_data)5868 check_field (GQuark field_id, const GValue * value, gpointer user_data)
5869 {
5870   GstStructure *structure = (GstStructure *) user_data;
5871   const GValue *other = gst_structure_id_get_value (structure, field_id);
5872   const gchar *name = gst_structure_get_name (structure);
5873 
5874   if (g_str_has_prefix (name, "video/")) {
5875     /* ignore framerate with video caps */
5876     if (g_strcmp0 (g_quark_to_string (field_id), "framerate") == 0)
5877       return TRUE;
5878   }
5879 
5880   if (g_strcmp0 (name, "video/x-h264") == 0 ||
5881       g_strcmp0 (name, "video/x-h265") == 0) {
5882     /* We support muxing multiple codec_data structures, and the new SPS
5883      * will contain updated tier / level / profiles, which means we do
5884      * not need to fail renegotiation when those change.
5885      */
5886     if (field_is_in (field_id,
5887             "codec_data", "tier", "level", "profile",
5888             "chroma-site", "chroma-format", "bit-depth-luma", "colorimetry",
5889             /* TODO: this may require a separate track but gst, vlc, ffmpeg and
5890              * browsers work with this so... */
5891             "width", "height", NULL)) {
5892 
5893       return TRUE;
5894     }
5895   }
5896 
5897   if (other == NULL) {
5898     if (field_is_in (field_id, "interlace-mode", NULL) &&
5899         !g_strcmp0 (g_value_get_string (value), "progressive")) {
5900       return TRUE;
5901     }
5902     return FALSE;
5903   }
5904 
5905   return gst_value_compare (value, other) == GST_VALUE_EQUAL;
5906 }
5907 
5908 static gboolean
gst_qtmux_caps_is_subset_full(GstQTMux * qtmux,GstCaps * subset,GstCaps * superset)5909 gst_qtmux_caps_is_subset_full (GstQTMux * qtmux, GstCaps * subset,
5910     GstCaps * superset)
5911 {
5912   GstStructure *sub_s = gst_caps_get_structure (subset, 0);
5913   GstStructure *sup_s = gst_caps_get_structure (superset, 0);
5914 
5915   if (!gst_structure_has_name (sup_s, gst_structure_get_name (sub_s)))
5916     return FALSE;
5917 
5918   return gst_structure_foreach (sub_s, check_field, sup_s);
5919 }
5920 
5921 /* will unref @qtmux */
5922 static gboolean
gst_qt_mux_can_renegotiate(GstQTMux * qtmux,GstPad * pad,GstCaps * caps)5923 gst_qt_mux_can_renegotiate (GstQTMux * qtmux, GstPad * pad, GstCaps * caps)
5924 {
5925   GstQTMuxPad *qtmuxpad = GST_QT_MUX_PAD_CAST (pad);
5926 
5927   /* does not go well to renegotiate stream mid-way, unless
5928    * the old caps are a subset of the new one (this means upstream
5929    * added more info to the caps, as both should be 'fixed' caps) */
5930 
5931   if (!qtmuxpad->configured_caps) {
5932     GST_DEBUG_OBJECT (qtmux, "pad %s accepted caps %" GST_PTR_FORMAT,
5933         GST_PAD_NAME (pad), caps);
5934     return TRUE;
5935   }
5936 
5937   g_assert (caps != NULL);
5938 
5939   if (!gst_qtmux_caps_is_subset_full (qtmux, qtmuxpad->configured_caps, caps)) {
5940     GST_WARNING_OBJECT (qtmux,
5941         "pad %s refused renegotiation to %" GST_PTR_FORMAT " from %"
5942         GST_PTR_FORMAT, GST_PAD_NAME (pad), caps, qtmuxpad->configured_caps);
5943     return FALSE;
5944   }
5945 
5946   GST_DEBUG_OBJECT (qtmux,
5947       "pad %s accepted renegotiation to %" GST_PTR_FORMAT " from %"
5948       GST_PTR_FORMAT, GST_PAD_NAME (pad), caps, qtmuxpad->configured_caps);
5949 
5950   return TRUE;
5951 }
5952 
5953 static gboolean
gst_qt_mux_audio_sink_set_caps(GstQTMuxPad * qtpad,GstCaps * caps)5954 gst_qt_mux_audio_sink_set_caps (GstQTMuxPad * qtpad, GstCaps * caps)
5955 {
5956   GstPad *pad = GST_PAD (qtpad);
5957   GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
5958   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
5959   GstStructure *structure;
5960   const gchar *mimetype;
5961   gint rate, channels;
5962   const GValue *value = NULL;
5963   const GstBuffer *codec_data = NULL;
5964   GstQTMuxFormat format;
5965   AudioSampleEntry entry = { 0, };
5966   AtomInfo *ext_atom = NULL;
5967   gint constant_size = 0;
5968   const gchar *stream_format;
5969   guint32 timescale;
5970 
5971   GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
5972       GST_DEBUG_PAD_NAME (pad), caps);
5973 
5974   qtpad->prepare_buf_func = NULL;
5975 
5976   format = qtmux_klass->format;
5977   structure = gst_caps_get_structure (caps, 0);
5978   mimetype = gst_structure_get_name (structure);
5979 
5980   /* common info */
5981   if (!gst_structure_get_int (structure, "channels", &channels) ||
5982       !gst_structure_get_int (structure, "rate", &rate)) {
5983     goto refuse_caps;
5984   }
5985 
5986   /* optional */
5987   value = gst_structure_get_value (structure, "codec_data");
5988   if (value != NULL)
5989     codec_data = gst_value_get_buffer (value);
5990 
5991   qtpad->is_out_of_order = FALSE;
5992 
5993   /* set common properties */
5994   entry.sample_rate = rate;
5995   entry.channels = channels;
5996   /* default */
5997   entry.sample_size = 16;
5998   /* this is the typical compressed case */
5999   if (format == GST_QT_MUX_FORMAT_QT) {
6000     entry.version = 1;
6001     entry.compression_id = -2;
6002   }
6003 
6004   /* now map onto a fourcc, and some extra properties */
6005   if (strcmp (mimetype, "audio/mpeg") == 0) {
6006     gint mpegversion = 0, mpegaudioversion = 0;
6007     gint layer = -1;
6008 
6009     gst_structure_get_int (structure, "mpegversion", &mpegversion);
6010     switch (mpegversion) {
6011       case 1:
6012         gst_structure_get_int (structure, "layer", &layer);
6013         gst_structure_get_int (structure, "mpegaudioversion",
6014             &mpegaudioversion);
6015 
6016         /* mp1/2/3 */
6017         /* note: QuickTime player does not like mp3 either way in iso/mp4 */
6018         if (format == GST_QT_MUX_FORMAT_QT)
6019           entry.fourcc = FOURCC__mp3;
6020         else {
6021           entry.fourcc = FOURCC_mp4a;
6022           ext_atom =
6023               build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG1_P3,
6024               ESDS_STREAM_TYPE_AUDIO, codec_data, qtpad->avg_bitrate,
6025               qtpad->max_bitrate);
6026         }
6027         if (layer == 1) {
6028           g_warn_if_fail (format == GST_QT_MUX_FORMAT_MP4
6029               || format == GST_QT_MUX_FORMAT_QT);
6030           entry.samples_per_packet = 384;
6031         } else if (layer == 2) {
6032           g_warn_if_fail (format == GST_QT_MUX_FORMAT_MP4
6033               || format == GST_QT_MUX_FORMAT_QT);
6034           entry.samples_per_packet = 1152;
6035         } else {
6036           g_warn_if_fail (layer == 3);
6037           entry.samples_per_packet = (mpegaudioversion <= 1) ? 1152 : 576;
6038         }
6039         entry.bytes_per_sample = 2;
6040         break;
6041       case 4:
6042 
6043         /* check stream-format */
6044         stream_format = gst_structure_get_string (structure, "stream-format");
6045         if (stream_format) {
6046           if (strcmp (stream_format, "raw") != 0) {
6047             GST_WARNING_OBJECT (qtmux, "Unsupported AAC stream-format %s, "
6048                 "please use 'raw'", stream_format);
6049             goto refuse_caps;
6050           }
6051         } else {
6052           GST_WARNING_OBJECT (qtmux, "No stream-format present in caps, "
6053               "assuming 'raw'");
6054         }
6055 
6056         if (!codec_data || gst_buffer_get_size ((GstBuffer *) codec_data) < 2) {
6057           GST_WARNING_OBJECT (qtmux, "no (valid) codec_data for AAC audio");
6058           goto refuse_caps;
6059         } else {
6060           guint8 profile;
6061 
6062           gst_buffer_extract ((GstBuffer *) codec_data, 0, &profile, 1);
6063           /* warn if not Low Complexity profile */
6064           profile >>= 3;
6065           if (profile != 2)
6066             GST_WARNING_OBJECT (qtmux,
6067                 "non-LC AAC may not run well on (Apple) QuickTime/iTunes");
6068         }
6069 
6070         /* AAC */
6071         entry.fourcc = FOURCC_mp4a;
6072 
6073         if (format == GST_QT_MUX_FORMAT_QT)
6074           ext_atom = build_mov_aac_extension (qtpad->trak, codec_data,
6075               qtpad->avg_bitrate, qtpad->max_bitrate);
6076         else
6077           ext_atom =
6078               build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P3,
6079               ESDS_STREAM_TYPE_AUDIO, codec_data, qtpad->avg_bitrate,
6080               qtpad->max_bitrate);
6081         break;
6082       default:
6083         break;
6084     }
6085   } else if (strcmp (mimetype, "audio/AMR") == 0) {
6086     entry.fourcc = FOURCC_samr;
6087     entry.sample_size = 16;
6088     entry.samples_per_packet = 160;
6089     entry.bytes_per_sample = 2;
6090     ext_atom = build_amr_extension ();
6091   } else if (strcmp (mimetype, "audio/AMR-WB") == 0) {
6092     entry.fourcc = FOURCC_sawb;
6093     entry.sample_size = 16;
6094     entry.samples_per_packet = 320;
6095     entry.bytes_per_sample = 2;
6096     ext_atom = build_amr_extension ();
6097   } else if (strcmp (mimetype, "audio/x-raw") == 0) {
6098     GstAudioInfo info;
6099 
6100     gst_audio_info_init (&info);
6101     if (!gst_audio_info_from_caps (&info, caps))
6102       goto refuse_caps;
6103 
6104     /* spec has no place for a distinction in these */
6105     if (info.finfo->width != info.finfo->depth) {
6106       GST_DEBUG_OBJECT (qtmux, "width must be same as depth!");
6107       goto refuse_caps;
6108     }
6109 
6110     if ((info.finfo->flags & GST_AUDIO_FORMAT_FLAG_SIGNED)) {
6111       if (info.finfo->endianness == G_LITTLE_ENDIAN)
6112         entry.fourcc = FOURCC_sowt;
6113       else if (info.finfo->endianness == G_BIG_ENDIAN)
6114         entry.fourcc = FOURCC_twos;
6115       else
6116         entry.fourcc = FOURCC_sowt;
6117       /* maximum backward compatibility; only new version for > 16 bit */
6118       if (info.finfo->depth <= 16)
6119         entry.version = 0;
6120       /* not compressed in any case */
6121       entry.compression_id = 0;
6122       /* QT spec says: max at 16 bit even if sample size were actually larger,
6123        * however, most players (e.g. QuickTime!) seem to disagree, so ... */
6124       entry.sample_size = info.finfo->depth;
6125       entry.bytes_per_sample = info.finfo->depth / 8;
6126       entry.samples_per_packet = 1;
6127       entry.bytes_per_packet = info.finfo->depth / 8;
6128       entry.bytes_per_frame = entry.bytes_per_packet * info.channels;
6129     } else {
6130       if (info.finfo->width == 8 && info.finfo->depth == 8) {
6131         /* fall back to old 8-bit version */
6132         entry.fourcc = FOURCC_raw_;
6133         entry.version = 0;
6134         entry.compression_id = 0;
6135         entry.sample_size = 8;
6136       } else {
6137         GST_DEBUG_OBJECT (qtmux, "non 8-bit PCM must be signed");
6138         goto refuse_caps;
6139       }
6140     }
6141     constant_size = (info.finfo->depth / 8) * info.channels;
6142   } else if (strcmp (mimetype, "audio/x-alaw") == 0) {
6143     entry.fourcc = FOURCC_alaw;
6144     entry.samples_per_packet = 1023;
6145     entry.bytes_per_sample = 2;
6146   } else if (strcmp (mimetype, "audio/x-mulaw") == 0) {
6147     entry.fourcc = FOURCC_ulaw;
6148     entry.samples_per_packet = 1023;
6149     entry.bytes_per_sample = 2;
6150   } else if (strcmp (mimetype, "audio/x-adpcm") == 0) {
6151     gint blocksize;
6152     if (!gst_structure_get_int (structure, "block_align", &blocksize)) {
6153       GST_DEBUG_OBJECT (qtmux, "broken caps, block_align missing");
6154       goto refuse_caps;
6155     }
6156     /* Currently only supports WAV-style IMA ADPCM, for which the codec id is
6157        0x11 */
6158     entry.fourcc = MS_WAVE_FOURCC (0x11);
6159     /* 4 byte header per channel (including one sample). 2 samples per byte
6160        remaining. Simplifying gives the following (samples per block per
6161        channel) */
6162     entry.samples_per_packet = 2 * blocksize / channels - 7;
6163     entry.bytes_per_sample = 2;
6164 
6165     entry.bytes_per_frame = blocksize;
6166     entry.bytes_per_packet = blocksize / channels;
6167     /* ADPCM has constant size packets */
6168     constant_size = 1;
6169     /* TODO: I don't really understand why this helps, but it does! Constant
6170      * size and compression_id of -2 seem to be incompatible, and other files
6171      * in the wild use this too. */
6172     entry.compression_id = -1;
6173 
6174     ext_atom = build_ima_adpcm_extension (channels, rate, blocksize);
6175   } else if (strcmp (mimetype, "audio/x-alac") == 0) {
6176     GstBuffer *codec_config;
6177     gint len;
6178     GstMapInfo map;
6179 
6180     entry.fourcc = FOURCC_alac;
6181     gst_buffer_map ((GstBuffer *) codec_data, &map, GST_MAP_READ);
6182     /* let's check if codec data already comes with 'alac' atom prefix */
6183     if (!codec_data || (len = map.size) < 28) {
6184       GST_DEBUG_OBJECT (qtmux, "broken caps, codec data missing");
6185       gst_buffer_unmap ((GstBuffer *) codec_data, &map);
6186       goto refuse_caps;
6187     }
6188     if (GST_READ_UINT32_LE (map.data + 4) == FOURCC_alac) {
6189       len -= 8;
6190       codec_config =
6191           gst_buffer_copy_region ((GstBuffer *) codec_data,
6192           GST_BUFFER_COPY_MEMORY, 8, len);
6193     } else {
6194       codec_config = gst_buffer_ref ((GstBuffer *) codec_data);
6195     }
6196     gst_buffer_unmap ((GstBuffer *) codec_data, &map);
6197     if (len != 28) {
6198       /* does not look good, but perhaps some trailing unneeded stuff */
6199       GST_WARNING_OBJECT (qtmux, "unexpected codec-data size, possibly broken");
6200     }
6201     if (format == GST_QT_MUX_FORMAT_QT)
6202       ext_atom = build_mov_alac_extension (codec_config);
6203     else
6204       ext_atom = build_codec_data_extension (FOURCC_alac, codec_config);
6205     /* set some more info */
6206     gst_buffer_map (codec_config, &map, GST_MAP_READ);
6207     entry.bytes_per_sample = 2;
6208     entry.samples_per_packet = GST_READ_UINT32_BE (map.data + 4);
6209     gst_buffer_unmap (codec_config, &map);
6210     gst_buffer_unref (codec_config);
6211   } else if (strcmp (mimetype, "audio/x-ac3") == 0) {
6212     entry.fourcc = FOURCC_ac_3;
6213 
6214     /* Fixed values according to TS 102 366 but it also mentions that
6215      * they should be ignored */
6216     entry.channels = 2;
6217     entry.sample_size = 16;
6218 
6219     /* AC-3 needs an extension atom but its data can only be obtained from
6220      * the stream itself. Abuse the prepare_buf_func so we parse a frame
6221      * and get the needed data */
6222     qtpad->prepare_buf_func = gst_qt_mux_prepare_parse_ac3_frame;
6223   } else if (strcmp (mimetype, "audio/x-opus") == 0) {
6224     /* Based on the specification defined in:
6225      * https://www.opus-codec.org/docs/opus_in_isobmff.html */
6226     guint8 channels, mapping_family, stream_count, coupled_count;
6227     guint16 pre_skip;
6228     gint16 output_gain;
6229     guint32 rate;
6230     guint8 channel_mapping[256];
6231     const GValue *streamheader;
6232     const GValue *first_element;
6233     GstBuffer *header;
6234 
6235     entry.fourcc = FOURCC_opus;
6236     entry.sample_size = 16;
6237 
6238     streamheader = gst_structure_get_value (structure, "streamheader");
6239     if (streamheader && GST_VALUE_HOLDS_ARRAY (streamheader) &&
6240         gst_value_array_get_size (streamheader) != 0) {
6241       first_element = gst_value_array_get_value (streamheader, 0);
6242       header = gst_value_get_buffer (first_element);
6243       if (!gst_codec_utils_opus_parse_header (header, &rate, &channels,
6244               &mapping_family, &stream_count, &coupled_count, channel_mapping,
6245               &pre_skip, &output_gain)) {
6246         GST_ERROR_OBJECT (qtmux, "Incomplete OpusHead");
6247         goto refuse_caps;
6248       }
6249     } else {
6250       GST_WARNING_OBJECT (qtmux,
6251           "no streamheader field in caps %" GST_PTR_FORMAT, caps);
6252 
6253       if (!gst_codec_utils_opus_parse_caps (caps, &rate, &channels,
6254               &mapping_family, &stream_count, &coupled_count,
6255               channel_mapping)) {
6256         GST_ERROR_OBJECT (qtmux, "Incomplete Opus caps");
6257         goto refuse_caps;
6258       }
6259       pre_skip = 0;
6260       output_gain = 0;
6261     }
6262 
6263     entry.channels = channels;
6264     ext_atom = build_opus_extension (rate, channels, mapping_family,
6265         stream_count, coupled_count, channel_mapping, pre_skip, output_gain);
6266   }
6267 
6268   if (!entry.fourcc)
6269     goto refuse_caps;
6270 
6271   timescale = gst_qt_mux_pad_get_timescale (GST_QT_MUX_PAD_CAST (pad));
6272   if (!timescale && qtmux->trak_timescale)
6273     timescale = qtmux->trak_timescale;
6274   else if (!timescale)
6275     timescale = entry.sample_rate;
6276 
6277   /* ok, set the pad info accordingly */
6278   qtpad->fourcc = entry.fourcc;
6279   qtpad->sample_size = constant_size;
6280   qtpad->trak_ste =
6281       (SampleTableEntry *) atom_trak_set_audio_type (qtpad->trak,
6282       qtmux->context, &entry, timescale, ext_atom, constant_size);
6283 
6284   gst_object_unref (qtmux);
6285   return TRUE;
6286 
6287   /* ERRORS */
6288 refuse_caps:
6289   {
6290     GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
6291         GST_PAD_NAME (pad), caps);
6292     gst_object_unref (qtmux);
6293     return FALSE;
6294   }
6295 }
6296 
6297 static gboolean
gst_qt_mux_video_sink_set_caps(GstQTMuxPad * qtpad,GstCaps * caps)6298 gst_qt_mux_video_sink_set_caps (GstQTMuxPad * qtpad, GstCaps * caps)
6299 {
6300   GstPad *pad = GST_PAD (qtpad);
6301   GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
6302   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
6303   GstStructure *structure;
6304   const gchar *mimetype;
6305   gint width, height, depth = -1;
6306   gint framerate_num, framerate_den;
6307   guint32 rate;
6308   const GValue *value = NULL;
6309   const GstBuffer *codec_data = NULL;
6310   VisualSampleEntry entry = { 0, };
6311   GstQTMuxFormat format;
6312   AtomInfo *ext_atom = NULL;
6313   GList *ext_atom_list = NULL;
6314   gboolean sync = FALSE;
6315   int par_num, par_den;
6316   const gchar *multiview_mode;
6317 
6318   GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
6319       GST_DEBUG_PAD_NAME (pad), caps);
6320 
6321   qtpad->prepare_buf_func = NULL;
6322 
6323   format = qtmux_klass->format;
6324   structure = gst_caps_get_structure (caps, 0);
6325   mimetype = gst_structure_get_name (structure);
6326 
6327   /* required parts */
6328   if (!gst_structure_get_int (structure, "width", &width) ||
6329       !gst_structure_get_int (structure, "height", &height))
6330     goto refuse_caps;
6331 
6332   /* optional */
6333   depth = -1;
6334   /* works as a default timebase */
6335   framerate_num = 10000;
6336   framerate_den = 1;
6337   gst_structure_get_fraction (structure, "framerate", &framerate_num,
6338       &framerate_den);
6339   gst_structure_get_int (structure, "depth", &depth);
6340   value = gst_structure_get_value (structure, "codec_data");
6341   if (value != NULL)
6342     codec_data = gst_value_get_buffer (value);
6343 
6344   par_num = 1;
6345   par_den = 1;
6346   gst_structure_get_fraction (structure, "pixel-aspect-ratio", &par_num,
6347       &par_den);
6348 
6349   qtpad->is_out_of_order = FALSE;
6350 
6351   /* bring frame numerator into a range that ensures both reasonable resolution
6352    * as well as a fair duration */
6353   qtpad->expected_sample_duration_n = framerate_num;
6354   qtpad->expected_sample_duration_d = framerate_den;
6355 
6356   rate = gst_qt_mux_pad_get_timescale (GST_QT_MUX_PAD_CAST (pad));
6357   if (!rate && qtmux->trak_timescale)
6358     rate = qtmux->trak_timescale;
6359   else if (!rate)
6360     rate = atom_framerate_to_timescale (framerate_num, framerate_den);
6361 
6362   GST_DEBUG_OBJECT (qtmux, "Rate of video track selected: %" G_GUINT32_FORMAT,
6363       rate);
6364 
6365   multiview_mode = gst_structure_get_string (structure, "multiview-mode");
6366   if (multiview_mode && !qtpad->trak->mdia.minf.stbl.svmi) {
6367     GstVideoMultiviewMode mode;
6368     GstVideoMultiviewFlags flags = 0;
6369 
6370     mode = gst_video_multiview_mode_from_caps_string (multiview_mode);
6371     gst_structure_get_flagset (structure,
6372         "multiview-flags", (guint *) & flags, NULL);
6373     switch (mode) {
6374       case GST_VIDEO_MULTIVIEW_MODE_MONO:
6375         /* Nothing to do for mono, just don't warn about it */
6376         break;
6377       case GST_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE:
6378         qtpad->trak->mdia.minf.stbl.svmi =
6379             atom_svmi_new (0,
6380             flags & GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_VIEW_FIRST);
6381         break;
6382       case GST_VIDEO_MULTIVIEW_MODE_ROW_INTERLEAVED:
6383         qtpad->trak->mdia.minf.stbl.svmi =
6384             atom_svmi_new (1,
6385             flags & GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_VIEW_FIRST);
6386         break;
6387       case GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME:
6388         qtpad->trak->mdia.minf.stbl.svmi =
6389             atom_svmi_new (2,
6390             flags & GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_VIEW_FIRST);
6391         break;
6392       default:
6393         GST_DEBUG_OBJECT (qtmux, "Unsupported multiview-mode %s",
6394             multiview_mode);
6395         break;
6396     }
6397   }
6398 
6399   /* set common properties */
6400   entry.width = width;
6401   entry.height = height;
6402   entry.par_n = par_num;
6403   entry.par_d = par_den;
6404   /* should be OK according to qt and iso spec, override if really needed */
6405   entry.color_table_id = -1;
6406   entry.frame_count = 1;
6407   entry.depth = 24;
6408 
6409   /* sync entries by default */
6410   sync = TRUE;
6411 
6412   /* now map onto a fourcc, and some extra properties */
6413   if (strcmp (mimetype, "video/x-raw") == 0) {
6414     const gchar *format;
6415     GstVideoFormat fmt;
6416     const GstVideoFormatInfo *vinfo;
6417 
6418     format = gst_structure_get_string (structure, "format");
6419     fmt = gst_video_format_from_string (format);
6420     vinfo = gst_video_format_get_info (fmt);
6421 
6422     switch (fmt) {
6423       case GST_VIDEO_FORMAT_UYVY:
6424         if (depth == -1)
6425           depth = 24;
6426         entry.fourcc = FOURCC_2vuy;
6427         entry.depth = depth;
6428         sync = FALSE;
6429         break;
6430       case GST_VIDEO_FORMAT_v210:
6431         if (depth == -1)
6432           depth = 24;
6433         entry.fourcc = FOURCC_v210;
6434         entry.depth = depth;
6435         sync = FALSE;
6436         break;
6437       default:
6438         if (GST_VIDEO_FORMAT_INFO_FLAGS (vinfo) & GST_VIDEO_FORMAT_FLAG_RGB) {
6439           entry.fourcc = FOURCC_raw_;
6440           entry.depth = GST_VIDEO_FORMAT_INFO_PSTRIDE (vinfo, 0) * 8;
6441           sync = FALSE;
6442         }
6443         break;
6444     }
6445   } else if (strcmp (mimetype, "video/x-h263") == 0) {
6446     ext_atom = NULL;
6447     if (format == GST_QT_MUX_FORMAT_QT)
6448       entry.fourcc = FOURCC_h263;
6449     else
6450       entry.fourcc = FOURCC_s263;
6451     ext_atom = build_h263_extension ();
6452     if (ext_atom != NULL)
6453       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
6454   } else if (strcmp (mimetype, "video/x-divx") == 0 ||
6455       strcmp (mimetype, "video/mpeg") == 0) {
6456     gint version = 0;
6457 
6458     if (strcmp (mimetype, "video/x-divx") == 0) {
6459       gst_structure_get_int (structure, "divxversion", &version);
6460       version = version == 5 ? 1 : 0;
6461     } else {
6462       gst_structure_get_int (structure, "mpegversion", &version);
6463       version = version == 4 ? 1 : 0;
6464     }
6465     if (version) {
6466       entry.fourcc = FOURCC_mp4v;
6467       ext_atom =
6468           build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P2,
6469           ESDS_STREAM_TYPE_VISUAL, codec_data, qtpad->avg_bitrate,
6470           qtpad->max_bitrate);
6471       if (ext_atom != NULL)
6472         ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
6473       if (!codec_data)
6474         GST_WARNING_OBJECT (qtmux, "no codec_data for MPEG4 video; "
6475             "output might not play in Apple QuickTime (try global-headers?)");
6476     }
6477   } else if (strcmp (mimetype, "video/x-h264") == 0) {
6478     const gchar *stream_format;
6479 
6480     if (!codec_data) {
6481       GST_WARNING_OBJECT (qtmux, "no codec_data in h264 caps");
6482       goto refuse_caps;
6483     }
6484 
6485     stream_format = gst_structure_get_string (structure, "stream-format");
6486 
6487     if (!g_strcmp0 (stream_format, "avc")) {
6488       entry.fourcc = FOURCC_avc1;
6489     } else if (!g_strcmp0 (stream_format, "avc3")) {
6490       entry.fourcc = FOURCC_avc3;
6491     } else {
6492       g_assert_not_reached ();
6493     }
6494 
6495     ext_atom = build_btrt_extension (0, qtpad->avg_bitrate, qtpad->max_bitrate);
6496     if (ext_atom != NULL)
6497       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
6498     ext_atom = build_codec_data_extension (FOURCC_avcC, codec_data);
6499     if (ext_atom != NULL)
6500       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
6501   } else if (strcmp (mimetype, "video/x-h265") == 0) {
6502     const gchar *format;
6503 
6504     if (!codec_data) {
6505       GST_WARNING_OBJECT (qtmux, "no codec_data in h265 caps");
6506       goto refuse_caps;
6507     }
6508 
6509     format = gst_structure_get_string (structure, "stream-format");
6510     if (strcmp (format, "hvc1") == 0)
6511       entry.fourcc = FOURCC_hvc1;
6512     else if (strcmp (format, "hev1") == 0)
6513       entry.fourcc = FOURCC_hev1;
6514 
6515     ext_atom = build_btrt_extension (0, qtpad->avg_bitrate, qtpad->max_bitrate);
6516     if (ext_atom != NULL)
6517       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
6518 
6519     ext_atom = build_codec_data_extension (FOURCC_hvcC, codec_data);
6520     if (ext_atom != NULL)
6521       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
6522 
6523   } else if (strcmp (mimetype, "video/x-svq") == 0) {
6524     gint version = 0;
6525     const GstBuffer *seqh = NULL;
6526     const GValue *seqh_value;
6527     gdouble gamma = 0;
6528 
6529     gst_structure_get_int (structure, "svqversion", &version);
6530     if (version == 3) {
6531       entry.fourcc = FOURCC_SVQ3;
6532       entry.version = 3;
6533       entry.depth = 32;
6534 
6535       seqh_value = gst_structure_get_value (structure, "seqh");
6536       if (seqh_value) {
6537         seqh = gst_value_get_buffer (seqh_value);
6538         ext_atom = build_SMI_atom (seqh);
6539         if (ext_atom)
6540           ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
6541       }
6542 
6543       /* we need to add the gamma anyway because quicktime might crash
6544        * when it doesn't find it */
6545       if (!gst_structure_get_double (structure, "applied-gamma", &gamma)) {
6546         /* it seems that using 0 here makes it ignored */
6547         gamma = 0.0;
6548       }
6549       ext_atom = build_gama_atom (gamma);
6550       if (ext_atom)
6551         ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
6552     } else {
6553       GST_WARNING_OBJECT (qtmux, "SVQ version %d not supported. Please file "
6554           "a bug at http://bugzilla.gnome.org", version);
6555     }
6556   } else if (strcmp (mimetype, "video/x-dv") == 0) {
6557     gint version = 0;
6558     gboolean pal = TRUE;
6559 
6560     sync = FALSE;
6561     if (framerate_num != 25 || framerate_den != 1)
6562       pal = FALSE;
6563     gst_structure_get_int (structure, "dvversion", &version);
6564     /* fall back to typical one */
6565     if (!version)
6566       version = 25;
6567     switch (version) {
6568       case 25:
6569         if (pal)
6570           entry.fourcc = FOURCC_dvcp;
6571         else
6572           entry.fourcc = FOURCC_dvc_;
6573         break;
6574       case 50:
6575         if (pal)
6576           entry.fourcc = FOURCC_dv5p;
6577         else
6578           entry.fourcc = FOURCC_dv5n;
6579         break;
6580       default:
6581         GST_WARNING_OBJECT (qtmux, "unrecognized dv version");
6582         break;
6583     }
6584   } else if (strcmp (mimetype, "image/jpeg") == 0) {
6585     entry.fourcc = FOURCC_jpeg;
6586     sync = FALSE;
6587   } else if (strcmp (mimetype, "image/png") == 0) {
6588     entry.fourcc = FOURCC_png;
6589     sync = FALSE;
6590   } else if (strcmp (mimetype, "image/x-j2c") == 0 ||
6591       strcmp (mimetype, "image/x-jpc") == 0) {
6592     const gchar *colorspace;
6593     const GValue *cmap_array;
6594     const GValue *cdef_array;
6595     gint ncomp = 0;
6596 
6597     if (strcmp (mimetype, "image/x-jpc") == 0) {
6598       qtpad->prepare_buf_func = gst_qt_mux_prepare_jpc_buffer;
6599     }
6600 
6601     gst_structure_get_int (structure, "num-components", &ncomp);
6602     cmap_array = gst_structure_get_value (structure, "component-map");
6603     cdef_array = gst_structure_get_value (structure, "channel-definitions");
6604 
6605     ext_atom = NULL;
6606     entry.fourcc = FOURCC_mjp2;
6607     sync = FALSE;
6608 
6609     colorspace = gst_structure_get_string (structure, "colorspace");
6610     if (colorspace &&
6611         (ext_atom =
6612             build_jp2h_extension (width, height, colorspace, ncomp, cmap_array,
6613                 cdef_array)) != NULL) {
6614       ext_atom_list = g_list_append (ext_atom_list, ext_atom);
6615 
6616       ext_atom = build_jp2x_extension (codec_data);
6617       if (ext_atom)
6618         ext_atom_list = g_list_append (ext_atom_list, ext_atom);
6619     } else {
6620       GST_DEBUG_OBJECT (qtmux, "missing or invalid fourcc in jp2 caps");
6621       goto refuse_caps;
6622     }
6623   } else if (strcmp (mimetype, "video/x-vp8") == 0) {
6624     entry.fourcc = FOURCC_vp08;
6625   } else if (strcmp (mimetype, "video/x-vp9") == 0) {
6626     entry.fourcc = FOURCC_vp09;
6627   } else if (strcmp (mimetype, "video/x-dirac") == 0) {
6628     entry.fourcc = FOURCC_drac;
6629   } else if (strcmp (mimetype, "video/x-qt-part") == 0) {
6630     guint32 fourcc = 0;
6631 
6632     gst_structure_get_uint (structure, "format", &fourcc);
6633     entry.fourcc = fourcc;
6634   } else if (strcmp (mimetype, "video/x-mp4-part") == 0) {
6635     guint32 fourcc = 0;
6636 
6637     gst_structure_get_uint (structure, "format", &fourcc);
6638     entry.fourcc = fourcc;
6639   } else if (strcmp (mimetype, "video/x-prores") == 0) {
6640     const gchar *variant;
6641 
6642     variant = gst_structure_get_string (structure, "variant");
6643     if (!variant || !g_strcmp0 (variant, "standard"))
6644       entry.fourcc = FOURCC_apcn;
6645     else if (!g_strcmp0 (variant, "lt"))
6646       entry.fourcc = FOURCC_apcs;
6647     else if (!g_strcmp0 (variant, "hq"))
6648       entry.fourcc = FOURCC_apch;
6649     else if (!g_strcmp0 (variant, "proxy"))
6650       entry.fourcc = FOURCC_apco;
6651     else if (!g_strcmp0 (variant, "4444"))
6652       entry.fourcc = FOURCC_ap4h;
6653     else if (!g_strcmp0 (variant, "4444xq"))
6654       entry.fourcc = FOURCC_ap4x;
6655 
6656     sync = FALSE;
6657 
6658     if (!qtmux->interleave_time_set)
6659       qtmux->interleave_time = 500 * GST_MSECOND;
6660     if (!qtmux->interleave_bytes_set)
6661       qtmux->interleave_bytes = width > 720 ? 4 * 1024 * 1024 : 2 * 1024 * 1024;
6662   } else if (strcmp (mimetype, "video/x-cineform") == 0) {
6663     entry.fourcc = FOURCC_cfhd;
6664     sync = FALSE;
6665   } else if (strcmp (mimetype, "video/x-av1") == 0) {
6666     gint presentation_delay;
6667     guint8 presentation_delay_byte = 0;
6668     GstBuffer *av1_codec_data;
6669 
6670     if (gst_structure_get_int (structure, "presentation-delay",
6671             &presentation_delay)) {
6672       presentation_delay_byte = 1 << 5;
6673       presentation_delay_byte |= MAX (0xF, presentation_delay & 0xF);
6674     }
6675 
6676 
6677     av1_codec_data = gst_buffer_new_allocate (NULL, 5, NULL);
6678     /* Fill version and 3 bytes of flags to 0 */
6679     gst_buffer_memset (av1_codec_data, 0, 0, 4);
6680     gst_buffer_fill (av1_codec_data, 4, &presentation_delay_byte, 1);
6681     if (codec_data)
6682       av1_codec_data = gst_buffer_append (av1_codec_data,
6683           gst_buffer_ref ((GstBuffer *) codec_data));
6684 
6685     entry.fourcc = FOURCC_av01;
6686 
6687     ext_atom = build_btrt_extension (0, qtpad->avg_bitrate, qtpad->max_bitrate);
6688     if (ext_atom != NULL)
6689       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
6690     ext_atom = build_codec_data_extension (FOURCC_av1C, av1_codec_data);
6691     if (ext_atom != NULL)
6692       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
6693     gst_buffer_unref (av1_codec_data);
6694   }
6695 
6696   if (!entry.fourcc)
6697     goto refuse_caps;
6698 
6699   if (qtmux_klass->format == GST_QT_MUX_FORMAT_QT ||
6700       qtmux_klass->format == GST_QT_MUX_FORMAT_MP4) {
6701     const gchar *s;
6702     GstVideoColorimetry colorimetry;
6703 
6704     s = gst_structure_get_string (structure, "colorimetry");
6705     if (s && gst_video_colorimetry_from_string (&colorimetry, s)) {
6706       ext_atom =
6707           build_colr_extension (&colorimetry,
6708           qtmux_klass->format == GST_QT_MUX_FORMAT_MP4);
6709       if (ext_atom)
6710         ext_atom_list = g_list_append (ext_atom_list, ext_atom);
6711     }
6712   }
6713 
6714   if (qtmux_klass->format == GST_QT_MUX_FORMAT_QT
6715       || strcmp (mimetype, "image/x-j2c") == 0
6716       || strcmp (mimetype, "image/x-jpc") == 0) {
6717     const gchar *s;
6718     GstVideoInterlaceMode interlace_mode;
6719     GstVideoFieldOrder field_order;
6720     gint fields = -1;
6721 
6722     if (strcmp (mimetype, "image/x-j2c") == 0 ||
6723         strcmp (mimetype, "image/x-jpc") == 0) {
6724 
6725       fields = 1;
6726       gst_structure_get_int (structure, "fields", &fields);
6727     }
6728 
6729     s = gst_structure_get_string (structure, "interlace-mode");
6730     if (s)
6731       interlace_mode = gst_video_interlace_mode_from_string (s);
6732     else
6733       interlace_mode =
6734           (fields <=
6735           1) ? GST_VIDEO_INTERLACE_MODE_PROGRESSIVE :
6736           GST_VIDEO_INTERLACE_MODE_MIXED;
6737 
6738     field_order = GST_VIDEO_FIELD_ORDER_UNKNOWN;
6739     if (interlace_mode == GST_VIDEO_INTERLACE_MODE_INTERLEAVED) {
6740       s = gst_structure_get_string (structure, "field-order");
6741       if (s)
6742         field_order = gst_video_field_order_from_string (s);
6743     }
6744 
6745     ext_atom = build_fiel_extension (interlace_mode, field_order);
6746     if (ext_atom)
6747       ext_atom_list = g_list_append (ext_atom_list, ext_atom);
6748   }
6749 
6750 
6751   if (qtmux_klass->format == GST_QT_MUX_FORMAT_QT &&
6752       width > 640 && width <= 1052 && height >= 480 && height <= 576) {
6753     /* The 'clap' extension is also defined for MP4 but inventing values in
6754      * general seems a bit tricky for this one. We only write it for
6755      * SD resolution in MOV, where it is a requirement.
6756      * The same goes for the 'tapt' extension, just that it is not defined for
6757      * MP4 and only for MOV
6758      */
6759     gint dar_num, dar_den;
6760     gint clef_width, clef_height, prof_width;
6761     gint clap_width_n, clap_width_d, clap_height;
6762     gint cdiv;
6763     double approx_dar;
6764 
6765     /* First, guess display aspect ratio based on pixel aspect ratio,
6766      * width and height. We assume that display aspect ratio is either
6767      * 4:3 or 16:9
6768      */
6769     approx_dar = (gdouble) (width * par_num) / (height * par_den);
6770     if (approx_dar > 11.0 / 9 && approx_dar < 14.0 / 9) {
6771       dar_num = 4;
6772       dar_den = 3;
6773     } else if (approx_dar > 15.0 / 9 && approx_dar < 18.0 / 9) {
6774       dar_num = 16;
6775       dar_den = 9;
6776     } else {
6777       dar_num = width * par_num;
6778       dar_den = height * par_den;
6779       cdiv = gst_util_greatest_common_divisor (dar_num, dar_den);
6780       dar_num /= cdiv;
6781       dar_den /= cdiv;
6782     }
6783 
6784     /* Then, calculate clean-aperture values (clap and clef)
6785      * using the guessed DAR.
6786      */
6787     clef_height = clap_height = (height == 486 ? 480 : height);
6788     clef_width = gst_util_uint64_scale (clef_height,
6789         dar_num * G_GUINT64_CONSTANT (65536), dar_den);
6790     prof_width = gst_util_uint64_scale (width,
6791         par_num * G_GUINT64_CONSTANT (65536), par_den);
6792     clap_width_n = clap_height * dar_num * par_den;
6793     clap_width_d = dar_den * par_num;
6794     cdiv = gst_util_greatest_common_divisor (clap_width_n, clap_width_d);
6795     clap_width_n /= cdiv;
6796     clap_width_d /= cdiv;
6797 
6798     ext_atom = build_tapt_extension (clef_width, clef_height << 16, prof_width,
6799         height << 16, width << 16, height << 16);
6800     qtpad->trak->tapt = ext_atom;
6801 
6802     ext_atom = build_clap_extension (clap_width_n, clap_width_d,
6803         clap_height, 1, 0, 1, 0, 1);
6804     if (ext_atom)
6805       ext_atom_list = g_list_append (ext_atom_list, ext_atom);
6806   }
6807 
6808   /* ok, set the pad info accordingly */
6809   qtpad->fourcc = entry.fourcc;
6810   qtpad->sync = sync;
6811   qtpad->trak_ste =
6812       (SampleTableEntry *) atom_trak_set_video_type (qtpad->trak,
6813       qtmux->context, &entry, rate, ext_atom_list);
6814   if (strcmp (mimetype, "video/x-prores") == 0) {
6815     SampleTableEntryMP4V *mp4v = (SampleTableEntryMP4V *) qtpad->trak_ste;
6816     const gchar *compressor = NULL;
6817     mp4v->spatial_quality = 0x3FF;
6818     mp4v->temporal_quality = 0;
6819     mp4v->vendor = FOURCC_appl;
6820     mp4v->horizontal_resolution = 72 << 16;
6821     mp4v->vertical_resolution = 72 << 16;
6822     mp4v->depth = (entry.fourcc == FOURCC_ap4h
6823         || entry.fourcc == FOURCC_ap4x) ? (depth > 0 ? depth : 32) : 24;
6824 
6825     /* Set compressor name, required by some software */
6826     switch (entry.fourcc) {
6827       case FOURCC_apcn:
6828         compressor = "Apple ProRes 422";
6829         break;
6830       case FOURCC_apcs:
6831         compressor = "Apple ProRes 422 LT";
6832         break;
6833       case FOURCC_apch:
6834         compressor = "Apple ProRes 422 HQ";
6835         break;
6836       case FOURCC_apco:
6837         compressor = "Apple ProRes 422 Proxy";
6838         break;
6839       case FOURCC_ap4h:
6840         compressor = "Apple ProRes 4444";
6841         break;
6842       case FOURCC_ap4x:
6843         compressor = "Apple ProRes 4444 XQ";
6844         break;
6845     }
6846     if (compressor) {
6847       strcpy ((gchar *) mp4v->compressor + 1, compressor);
6848       mp4v->compressor[0] = strlen (compressor);
6849     }
6850   }
6851 
6852   gst_object_unref (qtmux);
6853   return TRUE;
6854 
6855   /* ERRORS */
6856 refuse_caps:
6857   {
6858     GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
6859         GST_PAD_NAME (pad), caps);
6860     gst_object_unref (qtmux);
6861     return FALSE;
6862   }
6863 }
6864 
6865 static gboolean
gst_qt_mux_subtitle_sink_set_caps(GstQTMuxPad * qtpad,GstCaps * caps)6866 gst_qt_mux_subtitle_sink_set_caps (GstQTMuxPad * qtpad, GstCaps * caps)
6867 {
6868   GstPad *pad = GST_PAD (qtpad);
6869   GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
6870   GstStructure *structure;
6871   SubtitleSampleEntry entry = { 0, };
6872 
6873   GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
6874       GST_DEBUG_PAD_NAME (pad), caps);
6875 
6876   /* subtitles default */
6877   subtitle_sample_entry_init (&entry);
6878   qtpad->is_out_of_order = FALSE;
6879   qtpad->sync = FALSE;
6880   qtpad->sparse = TRUE;
6881   qtpad->prepare_buf_func = NULL;
6882 
6883   structure = gst_caps_get_structure (caps, 0);
6884 
6885   if (gst_structure_has_name (structure, "text/x-raw")) {
6886     const gchar *format = gst_structure_get_string (structure, "format");
6887     if (format && strcmp (format, "utf8") == 0) {
6888       entry.fourcc = FOURCC_tx3g;
6889       qtpad->prepare_buf_func = gst_qt_mux_prepare_tx3g_buffer;
6890       qtpad->create_empty_buffer = gst_qt_mux_create_empty_tx3g_buffer;
6891     }
6892   }
6893 
6894   if (!entry.fourcc)
6895     goto refuse_caps;
6896 
6897   qtpad->fourcc = entry.fourcc;
6898   qtpad->trak_ste =
6899       (SampleTableEntry *) atom_trak_set_subtitle_type (qtpad->trak,
6900       qtmux->context, &entry);
6901 
6902   gst_object_unref (qtmux);
6903   return TRUE;
6904 
6905   /* ERRORS */
6906 refuse_caps:
6907   {
6908     GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
6909         GST_PAD_NAME (pad), caps);
6910     gst_object_unref (qtmux);
6911     return FALSE;
6912   }
6913 }
6914 
6915 static gboolean
gst_qt_mux_caption_sink_set_caps(GstQTMuxPad * qtpad,GstCaps * caps)6916 gst_qt_mux_caption_sink_set_caps (GstQTMuxPad * qtpad, GstCaps * caps)
6917 {
6918   GstPad *pad = GST_PAD (qtpad);
6919   GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
6920   GstStructure *structure;
6921   guint32 fourcc_entry;
6922   guint32 timescale;
6923 
6924   GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
6925       GST_DEBUG_PAD_NAME (pad), caps);
6926 
6927   /* captions default */
6928   qtpad->is_out_of_order = FALSE;
6929   qtpad->sync = FALSE;
6930   qtpad->sparse = TRUE;
6931   /* Closed caption data are within atoms */
6932   qtpad->prepare_buf_func = gst_qt_mux_prepare_caption_buffer;
6933 
6934   structure = gst_caps_get_structure (caps, 0);
6935 
6936   /* We know we only handle 608,format=s334-1a and 708,format=cdp */
6937   if (gst_structure_has_name (structure, "closedcaption/x-cea-608")) {
6938     fourcc_entry = FOURCC_c608;
6939   } else if (gst_structure_has_name (structure, "closedcaption/x-cea-708")) {
6940     fourcc_entry = FOURCC_c708;
6941   } else
6942     goto refuse_caps;
6943 
6944   /* We set the real timescale later to the one from the video track when
6945    * writing the headers */
6946   timescale = gst_qt_mux_pad_get_timescale (GST_QT_MUX_PAD_CAST (pad));
6947   if (!timescale && qtmux->trak_timescale)
6948     timescale = qtmux->trak_timescale;
6949   else if (!timescale)
6950     timescale = 30000;
6951 
6952   qtpad->fourcc = fourcc_entry;
6953   qtpad->trak_ste =
6954       (SampleTableEntry *) atom_trak_set_caption_type (qtpad->trak,
6955       qtmux->context, timescale, fourcc_entry);
6956 
6957   /* Initialize caption track language code to 0 unless something else is
6958    * specified. Without this, Final Cut considers it "non-standard"
6959    */
6960   qtpad->trak->mdia.mdhd.language_code = 0;
6961 
6962   gst_object_unref (qtmux);
6963   return TRUE;
6964 
6965   /* ERRORS */
6966 refuse_caps:
6967   {
6968     GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
6969         GST_PAD_NAME (pad), caps);
6970     gst_object_unref (qtmux);
6971     return FALSE;
6972   }
6973 }
6974 
6975 static GstFlowReturn
gst_qt_mux_sink_event_pre_queue(GstAggregator * agg,GstAggregatorPad * agg_pad,GstEvent * event)6976 gst_qt_mux_sink_event_pre_queue (GstAggregator * agg,
6977     GstAggregatorPad * agg_pad, GstEvent * event)
6978 {
6979   GstAggregatorClass *agg_class = GST_AGGREGATOR_CLASS (parent_class);
6980   GstQTMux *qtmux;
6981   GstFlowReturn ret = GST_FLOW_OK;
6982 
6983   qtmux = GST_QT_MUX_CAST (agg);
6984 
6985   if (GST_EVENT_TYPE (event) == GST_EVENT_CAPS) {
6986     GstCaps *caps;
6987 
6988     gst_event_parse_caps (event, &caps);
6989     if (!gst_qt_mux_can_renegotiate (qtmux, GST_PAD (agg_pad), caps)) {
6990       gst_event_unref (event);
6991       event = NULL;
6992       ret = GST_FLOW_NOT_NEGOTIATED;
6993     }
6994   }
6995 
6996   if (event != NULL)
6997     ret = agg_class->sink_event_pre_queue (agg, agg_pad, event);
6998 
6999   return ret;
7000 }
7001 
7002 
7003 static gboolean
gst_qt_mux_sink_event(GstAggregator * agg,GstAggregatorPad * agg_pad,GstEvent * event)7004 gst_qt_mux_sink_event (GstAggregator * agg, GstAggregatorPad * agg_pad,
7005     GstEvent * event)
7006 {
7007   GstAggregatorClass *agg_class = GST_AGGREGATOR_CLASS (parent_class);
7008   GstQTMuxPad *qtmux_pad;
7009   GstQTMux *qtmux;
7010   guint32 avg_bitrate = 0, max_bitrate = 0;
7011   GstPad *pad = GST_PAD (agg_pad);
7012   gboolean ret = TRUE;
7013 
7014   qtmux = GST_QT_MUX_CAST (agg);
7015   qtmux_pad = GST_QT_MUX_PAD_CAST (agg_pad);
7016 
7017   switch (GST_EVENT_TYPE (event)) {
7018     case GST_EVENT_CAPS:
7019     {
7020       GstCaps *caps;
7021 
7022       gst_event_parse_caps (event, &caps);
7023 
7024       /* find stream data */
7025       g_assert (qtmux_pad->set_caps);
7026 
7027       /* depending on codec (h264/h265 for example), muxer will append a new
7028        * stsd entry per set_caps(), but it's not ideal if referenced fields
7029        * in caps is not updated from previous one.
7030        * Each set_caps() implementation can be more enhanced
7031        * so that we can avoid duplicated atoms though, this identical caps
7032        * case is one we can skip obviously */
7033       if (qtmux_pad->configured_caps &&
7034           gst_caps_is_equal (qtmux_pad->configured_caps, caps)) {
7035         GST_DEBUG_OBJECT (qtmux_pad, "Ignore duplicated caps %" GST_PTR_FORMAT,
7036             caps);
7037       } else {
7038         ret = qtmux_pad->set_caps (qtmux_pad, caps);
7039 
7040         GST_OBJECT_LOCK (qtmux);
7041         if (qtmux->current_pad == qtmux_pad) {
7042           qtmux->current_chunk_offset = -1;
7043           qtmux->current_chunk_size = 0;
7044           qtmux->current_chunk_duration = 0;
7045         }
7046         GST_OBJECT_UNLOCK (qtmux);
7047       }
7048 
7049       if (ret)
7050         gst_caps_replace (&qtmux_pad->configured_caps, caps);
7051 
7052       gst_event_unref (event);
7053       event = NULL;
7054       break;
7055     }
7056     case GST_EVENT_TAG:{
7057       GstTagList *list;
7058       GstTagSetter *setter = GST_TAG_SETTER (qtmux);
7059       GstTagMergeMode mode;
7060       gchar *code;
7061 
7062       GST_OBJECT_LOCK (qtmux);
7063       mode = gst_tag_setter_get_tag_merge_mode (setter);
7064 
7065       gst_event_parse_tag (event, &list);
7066       GST_DEBUG_OBJECT (qtmux, "received tag event on pad %s:%s : %"
7067           GST_PTR_FORMAT, GST_DEBUG_PAD_NAME (pad), list);
7068 
7069       if (gst_tag_list_get_scope (list) == GST_TAG_SCOPE_GLOBAL) {
7070         gst_tag_setter_merge_tags (setter, list, mode);
7071         qtmux->tags_changed = TRUE;
7072       } else {
7073         if (!qtmux_pad->tags)
7074           qtmux_pad->tags = gst_tag_list_new_empty ();
7075         gst_tag_list_insert (qtmux_pad->tags, list, mode);
7076         qtmux_pad->tags_changed = TRUE;
7077       }
7078       GST_OBJECT_UNLOCK (qtmux);
7079 
7080       if (gst_tag_list_get_uint (list, GST_TAG_BITRATE, &avg_bitrate) |
7081           gst_tag_list_get_uint (list, GST_TAG_MAXIMUM_BITRATE, &max_bitrate)) {
7082         if (avg_bitrate > 0 && avg_bitrate < G_MAXUINT32)
7083           qtmux_pad->avg_bitrate = avg_bitrate;
7084         if (max_bitrate > 0 && max_bitrate < G_MAXUINT32)
7085           qtmux_pad->max_bitrate = max_bitrate;
7086       }
7087 
7088       if (gst_tag_list_get_string (list, GST_TAG_LANGUAGE_CODE, &code)) {
7089         const char *iso_code = gst_tag_get_language_code_iso_639_2T (code);
7090         if (iso_code) {
7091           if (qtmux_pad->trak) {
7092             /* https://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap4/qtff4.html */
7093             qtmux_pad->trak->mdia.mdhd.language_code = language_code (iso_code);
7094           }
7095         }
7096         g_free (code);
7097       }
7098 
7099       gst_event_unref (event);
7100       event = NULL;
7101       ret = TRUE;
7102       break;
7103     }
7104 /* ohos.opt.compat.0011
7105  * qtmux itself does not handle flush events, so in extreme cases, the buffer is discarded by gstpad
7106  * when it is passed forward, but qtmux thinks that the buffer writes the file successfully,
7107  * resulting in a file exception.
7108  */
7109 #ifdef OHOS_OPT_COMPAT
7110     case GST_EVENT_FLUSH_START: {
7111       g_mutex_lock(&qtmux->flush_lock);
7112       qtmux->is_flushing = TRUE;
7113       g_mutex_unlock(&qtmux->flush_lock);
7114       break;
7115     }
7116     case GST_EVENT_FLUSH_STOP: {
7117       g_mutex_lock(&qtmux->flush_lock);
7118       qtmux->is_flushing = FALSE;
7119       g_mutex_unlock(&qtmux->flush_lock);
7120       break;
7121     }
7122 #endif
7123     default:
7124       break;
7125   }
7126 
7127   if (event != NULL)
7128     ret = agg_class->sink_event (agg, agg_pad, event);
7129 
7130   return ret;
7131 }
7132 
7133 static void
gst_qt_mux_release_pad(GstElement * element,GstPad * pad)7134 gst_qt_mux_release_pad (GstElement * element, GstPad * pad)
7135 {
7136   GstQTMux *mux = GST_QT_MUX_CAST (element);
7137   GstQTMuxPad *muxpad = GST_QT_MUX_PAD_CAST (pad);
7138 
7139   GST_DEBUG_OBJECT (element, "Releasing %s:%s", GST_DEBUG_PAD_NAME (pad));
7140 
7141   /* Take a ref to the pad so we can clean it up after removing it from the element */
7142   pad = gst_object_ref (pad);
7143 
7144   /* Do aggregate level cleanup */
7145   GST_ELEMENT_CLASS (parent_class)->release_pad (element, pad);
7146 
7147   GST_OBJECT_LOCK (mux);
7148   if (mux->current_pad && GST_PAD (mux->current_pad) == pad) {
7149     mux->current_pad = NULL;
7150     mux->current_chunk_size = 0;
7151     mux->current_chunk_duration = 0;
7152   }
7153 
7154   gst_qt_mux_pad_reset (muxpad);
7155 
7156   if (GST_ELEMENT (mux)->sinkpads == NULL) {
7157     /* No more outstanding request pads, reset our counters */
7158     mux->video_pads = 0;
7159     mux->audio_pads = 0;
7160     mux->subtitle_pads = 0;
7161   }
7162   GST_OBJECT_UNLOCK (mux);
7163 
7164   gst_object_unref (pad);
7165 }
7166 
7167 static GstAggregatorPad *
gst_qt_mux_create_new_pad(GstAggregator * self,GstPadTemplate * templ,const gchar * req_name,const GstCaps * caps)7168 gst_qt_mux_create_new_pad (GstAggregator * self,
7169     GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps)
7170 {
7171   return g_object_new (GST_TYPE_QT_MUX_PAD, "name", req_name, "direction",
7172       templ->direction, "template", templ, NULL);
7173 }
7174 
7175 static GstPad *
gst_qt_mux_request_new_pad(GstElement * element,GstPadTemplate * templ,const gchar * req_name,const GstCaps * caps)7176 gst_qt_mux_request_new_pad (GstElement * element,
7177     GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps)
7178 {
7179   GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
7180   GstQTMux *qtmux = GST_QT_MUX_CAST (element);
7181   GstQTMuxPad *qtpad;
7182   GstQTPadSetCapsFunc setcaps_func;
7183   gchar *name;
7184   gint pad_id;
7185 
7186   if (templ->direction != GST_PAD_SINK)
7187     goto wrong_direction;
7188 
7189   if (qtmux->state > GST_QT_MUX_STATE_STARTED)
7190     goto too_late;
7191 
7192   if (templ == gst_element_class_get_pad_template (klass, "audio_%u")) {
7193     setcaps_func = gst_qt_mux_audio_sink_set_caps;
7194     if (req_name != NULL && sscanf (req_name, "audio_%u", &pad_id) == 1) {
7195       name = g_strdup (req_name);
7196     } else {
7197       name = g_strdup_printf ("audio_%u", qtmux->audio_pads++);
7198     }
7199   } else if (templ == gst_element_class_get_pad_template (klass, "video_%u")) {
7200     setcaps_func = gst_qt_mux_video_sink_set_caps;
7201     if (req_name != NULL && sscanf (req_name, "video_%u", &pad_id) == 1) {
7202       name = g_strdup (req_name);
7203     } else {
7204       name = g_strdup_printf ("video_%u", qtmux->video_pads++);
7205     }
7206   } else if (templ == gst_element_class_get_pad_template (klass, "subtitle_%u")) {
7207     setcaps_func = gst_qt_mux_subtitle_sink_set_caps;
7208     if (req_name != NULL && sscanf (req_name, "subtitle_%u", &pad_id) == 1) {
7209       name = g_strdup (req_name);
7210     } else {
7211       name = g_strdup_printf ("subtitle_%u", qtmux->subtitle_pads++);
7212     }
7213   } else if (templ == gst_element_class_get_pad_template (klass, "caption_%u")) {
7214     setcaps_func = gst_qt_mux_caption_sink_set_caps;
7215     if (req_name != NULL && sscanf (req_name, "caption_%u", &pad_id) == 1) {
7216       name = g_strdup (req_name);
7217     } else {
7218       name = g_strdup_printf ("caption_%u", qtmux->caption_pads++);
7219     }
7220   } else
7221     goto wrong_template;
7222 
7223   GST_DEBUG_OBJECT (qtmux, "Requested pad: %s", name);
7224 
7225   qtpad = (GstQTMuxPad *)
7226       GST_ELEMENT_CLASS (parent_class)->request_new_pad (element,
7227       templ, name, caps);
7228 
7229   g_free (name);
7230 
7231   /* set up pad */
7232   GST_OBJECT_LOCK (qtmux);
7233   gst_qt_mux_pad_reset (qtpad);
7234   qtpad->trak = atom_trak_new (qtmux->context);
7235 
7236   atom_moov_add_trak (qtmux->moov, qtpad->trak);
7237   GST_OBJECT_UNLOCK (qtmux);
7238 
7239   /* set up pad functions */
7240   qtpad->set_caps = setcaps_func;
7241   qtpad->dts = G_MININT64;
7242 
7243   return GST_PAD (qtpad);
7244 
7245   /* ERRORS */
7246 wrong_direction:
7247   {
7248     GST_WARNING_OBJECT (qtmux, "Request pad that is not a SINK pad.");
7249     return NULL;
7250   }
7251 too_late:
7252   {
7253     GST_WARNING_OBJECT (qtmux, "Not providing request pad after stream start.");
7254     return NULL;
7255   }
7256 wrong_template:
7257   {
7258     GST_WARNING_OBJECT (qtmux, "This is not our template!");
7259     return NULL;
7260   }
7261 }
7262 
7263 static void
gst_qt_mux_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)7264 gst_qt_mux_get_property (GObject * object,
7265     guint prop_id, GValue * value, GParamSpec * pspec)
7266 {
7267   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
7268 
7269   GST_OBJECT_LOCK (qtmux);
7270   switch (prop_id) {
7271     case PROP_MOVIE_TIMESCALE:
7272       g_value_set_uint (value, qtmux->timescale);
7273       break;
7274     case PROP_TRAK_TIMESCALE:
7275       g_value_set_uint (value, qtmux->trak_timescale);
7276       break;
7277     case PROP_DO_CTTS:
7278       g_value_set_boolean (value, qtmux->guess_pts);
7279       break;
7280 #ifndef GST_REMOVE_DEPRECATED
7281     case PROP_DTS_METHOD:
7282       g_value_set_enum (value, qtmux->dts_method);
7283       break;
7284 #endif
7285     case PROP_FAST_START:
7286       g_value_set_boolean (value, qtmux->fast_start);
7287       break;
7288     case PROP_FAST_START_TEMP_FILE:
7289       g_value_set_string (value, qtmux->fast_start_file_path);
7290       break;
7291     case PROP_MOOV_RECOV_FILE:
7292       g_value_set_string (value, qtmux->moov_recov_file_path);
7293       break;
7294     case PROP_FRAGMENT_DURATION:
7295       g_value_set_uint (value, qtmux->fragment_duration);
7296       break;
7297     case PROP_RESERVED_MAX_DURATION:
7298       g_value_set_uint64 (value, qtmux->reserved_max_duration);
7299       break;
7300     case PROP_RESERVED_DURATION_REMAINING:
7301       if (qtmux->reserved_duration_remaining == GST_CLOCK_TIME_NONE)
7302         g_value_set_uint64 (value, qtmux->reserved_max_duration);
7303       else {
7304         GstClockTime remaining = qtmux->reserved_duration_remaining;
7305 
7306         /* Report the remaining space as the calculated remaining, minus
7307          * however much we've muxed since the last update */
7308         if (remaining > qtmux->muxed_since_last_update)
7309           remaining -= qtmux->muxed_since_last_update;
7310         else
7311           remaining = 0;
7312         GST_LOG_OBJECT (qtmux, "reserved duration remaining - reporting %"
7313             G_GUINT64_FORMAT "(%" G_GUINT64_FORMAT " - %" G_GUINT64_FORMAT,
7314             remaining, qtmux->reserved_duration_remaining,
7315             qtmux->muxed_since_last_update);
7316         g_value_set_uint64 (value, remaining);
7317       }
7318       break;
7319     case PROP_RESERVED_MOOV_UPDATE_PERIOD:
7320       g_value_set_uint64 (value, qtmux->reserved_moov_update_period);
7321       break;
7322     case PROP_RESERVED_BYTES_PER_SEC:
7323       g_value_set_uint (value, qtmux->reserved_bytes_per_sec_per_trak);
7324       break;
7325     case PROP_RESERVED_PREFILL:
7326       g_value_set_boolean (value, qtmux->reserved_prefill);
7327       break;
7328     case PROP_INTERLEAVE_BYTES:
7329       g_value_set_uint64 (value, qtmux->interleave_bytes);
7330       break;
7331     case PROP_INTERLEAVE_TIME:
7332       g_value_set_uint64 (value, qtmux->interleave_time);
7333       break;
7334     case PROP_FORCE_CHUNKS:
7335       g_value_set_boolean (value, qtmux->force_chunks);
7336       break;
7337     case PROP_MAX_RAW_AUDIO_DRIFT:
7338       g_value_set_uint64 (value, qtmux->max_raw_audio_drift);
7339       break;
7340     case PROP_START_GAP_THRESHOLD:
7341       g_value_set_uint64 (value, qtmux->start_gap_threshold);
7342       break;
7343     case PROP_FORCE_CREATE_TIMECODE_TRAK:
7344       g_value_set_boolean (value, qtmux->force_create_timecode_trak);
7345       break;
7346     case PROP_FRAGMENT_MODE:{
7347       GstQTMuxFragmentMode mode = qtmux->fragment_mode;
7348       if (mode == GST_QT_MUX_FRAGMENT_STREAMABLE)
7349         mode = GST_QT_MUX_FRAGMENT_DASH_OR_MSS;
7350       g_value_set_enum (value, mode);
7351       break;
7352     }
7353     default:
7354       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
7355       break;
7356   }
7357   GST_OBJECT_UNLOCK (qtmux);
7358 }
7359 
7360 static void
gst_qt_mux_generate_fast_start_file_path(GstQTMux * qtmux)7361 gst_qt_mux_generate_fast_start_file_path (GstQTMux * qtmux)
7362 {
7363   gchar *tmp;
7364 
7365   g_free (qtmux->fast_start_file_path);
7366   qtmux->fast_start_file_path = NULL;
7367 
7368   tmp = g_strdup_printf ("%s%d", "qtmux", g_random_int ());
7369   qtmux->fast_start_file_path = g_build_filename (g_get_tmp_dir (), tmp, NULL);
7370   g_free (tmp);
7371 }
7372 
7373 static void
gst_qt_mux_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)7374 gst_qt_mux_set_property (GObject * object,
7375     guint prop_id, const GValue * value, GParamSpec * pspec)
7376 {
7377   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
7378 
7379   GST_OBJECT_LOCK (qtmux);
7380   switch (prop_id) {
7381     case PROP_MOVIE_TIMESCALE:
7382       qtmux->timescale = g_value_get_uint (value);
7383       break;
7384     case PROP_TRAK_TIMESCALE:
7385       qtmux->trak_timescale = g_value_get_uint (value);
7386       break;
7387     case PROP_DO_CTTS:
7388       qtmux->guess_pts = g_value_get_boolean (value);
7389       break;
7390 #ifndef GST_REMOVE_DEPRECATED
7391     case PROP_DTS_METHOD:
7392       qtmux->dts_method = g_value_get_enum (value);
7393       break;
7394 #endif
7395     case PROP_FAST_START:
7396       qtmux->fast_start = g_value_get_boolean (value);
7397       break;
7398     case PROP_FAST_START_TEMP_FILE:
7399       g_free (qtmux->fast_start_file_path);
7400       qtmux->fast_start_file_path = g_value_dup_string (value);
7401       /* NULL means to generate a random one */
7402       if (!qtmux->fast_start_file_path) {
7403         gst_qt_mux_generate_fast_start_file_path (qtmux);
7404       }
7405       break;
7406     case PROP_MOOV_RECOV_FILE:
7407       g_free (qtmux->moov_recov_file_path);
7408       qtmux->moov_recov_file_path = g_value_dup_string (value);
7409       break;
7410     case PROP_FRAGMENT_DURATION:
7411       qtmux->fragment_duration = g_value_get_uint (value);
7412       break;
7413     case PROP_RESERVED_MAX_DURATION:
7414       qtmux->reserved_max_duration = g_value_get_uint64 (value);
7415       break;
7416     case PROP_RESERVED_MOOV_UPDATE_PERIOD:
7417       qtmux->reserved_moov_update_period = g_value_get_uint64 (value);
7418       break;
7419     case PROP_RESERVED_BYTES_PER_SEC:
7420       qtmux->reserved_bytes_per_sec_per_trak = g_value_get_uint (value);
7421       break;
7422     case PROP_RESERVED_PREFILL:
7423       qtmux->reserved_prefill = g_value_get_boolean (value);
7424       break;
7425     case PROP_INTERLEAVE_BYTES:
7426       qtmux->interleave_bytes = g_value_get_uint64 (value);
7427       qtmux->interleave_bytes_set = TRUE;
7428       break;
7429     case PROP_INTERLEAVE_TIME:
7430       qtmux->interleave_time = g_value_get_uint64 (value);
7431       qtmux->interleave_time_set = TRUE;
7432       break;
7433     case PROP_FORCE_CHUNKS:
7434       qtmux->force_chunks = g_value_get_boolean (value);
7435       break;
7436     case PROP_MAX_RAW_AUDIO_DRIFT:
7437       qtmux->max_raw_audio_drift = g_value_get_uint64 (value);
7438       break;
7439     case PROP_START_GAP_THRESHOLD:
7440       qtmux->start_gap_threshold = g_value_get_uint64 (value);
7441       break;
7442     case PROP_FORCE_CREATE_TIMECODE_TRAK:
7443       qtmux->force_create_timecode_trak = g_value_get_boolean (value);
7444       qtmux->context->force_create_timecode_trak =
7445           qtmux->force_create_timecode_trak;
7446       break;
7447     case PROP_FRAGMENT_MODE:{
7448       GstQTMuxFragmentMode mode = g_value_get_enum (value);
7449       if (mode != GST_QT_MUX_FRAGMENT_STREAMABLE)
7450         qtmux->fragment_mode = mode;
7451       break;
7452     }
7453 /* ohos.ext.func.0016
7454  * add additional features to set geographic location information in mp4 file
7455  * enable_geolocation is the flag to enable this feature
7456  * latitudex10000 is the latitude to set, multiply 10000 for the convenience of calculation.
7457  * longitudex10000 is the longitude to set, multiply 10000 for the convenience of calculation.
7458  */
7459 #ifdef OHOS_EXT_FUNC
7460     case PROP_SET_LATITUDE:
7461       qtmux->latitudex10000 = g_value_get_int(value);
7462       qtmux->enable_geolocation = TRUE;
7463       break;
7464     case PROP_SET_LONGITUDE:
7465       qtmux->longitudex10000 = g_value_get_int(value);
7466       break;
7467 #endif
7468 /* ohos.ext.func.0018
7469  * add additional features to set orientationHint in mp4 file.
7470  * PROP_ROTAION_ANGLE is the angle to set, must be{0, 90, 180, 270}.
7471  */
7472 #ifdef OHOS_EXT_FUNC
7473     case PROP_ROTAION_ANGLE:
7474       qtmux->rotation = g_value_get_uint(value);
7475       break;
7476 #endif
7477     default:
7478       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
7479       break;
7480   }
7481   GST_OBJECT_UNLOCK (qtmux);
7482 }
7483 
7484 static gboolean
gst_qt_mux_start(GstAggregator * agg)7485 gst_qt_mux_start (GstAggregator * agg)
7486 {
7487   GstQTMux *qtmux = GST_QT_MUX_CAST (agg);
7488   GstSegment segment;
7489 
7490   qtmux->state = GST_QT_MUX_STATE_STARTED;
7491 
7492   /* let downstream know we think in BYTES and expect to do seeking later on */
7493   gst_segment_init (&segment, GST_FORMAT_BYTES);
7494   gst_aggregator_update_segment (agg, &segment);
7495 
7496   return TRUE;
7497 }
7498 
7499 static gboolean
gst_qt_mux_stop(GstAggregator * agg)7500 gst_qt_mux_stop (GstAggregator * agg)
7501 {
7502   GstQTMux *qtmux = GST_QT_MUX_CAST (agg);
7503 
7504   gst_qt_mux_reset (qtmux, TRUE);
7505 
7506   return TRUE;
7507 }
7508 
7509 enum
7510 {
7511   PROP_SUBCLASS_STREAMABLE = 1,
7512 };
7513 
7514 static void
gst_qt_mux_subclass_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)7515 gst_qt_mux_subclass_set_property (GObject * object,
7516     guint prop_id, const GValue * value, GParamSpec * pspec)
7517 {
7518   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
7519 
7520   GST_OBJECT_LOCK (qtmux);
7521   switch (prop_id) {
7522     case PROP_SUBCLASS_STREAMABLE:{
7523       GstQTMuxClass *qtmux_klass =
7524           (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
7525       if (qtmux_klass->format == GST_QT_MUX_FORMAT_ISML) {
7526         qtmux->streamable = g_value_get_boolean (value);
7527       }
7528       break;
7529     }
7530     default:
7531       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
7532       break;
7533   }
7534   GST_OBJECT_UNLOCK (qtmux);
7535 }
7536 
7537 static void
gst_qt_mux_subclass_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)7538 gst_qt_mux_subclass_get_property (GObject * object,
7539     guint prop_id, GValue * value, GParamSpec * pspec)
7540 {
7541   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
7542 
7543   GST_OBJECT_LOCK (qtmux);
7544   switch (prop_id) {
7545     case PROP_SUBCLASS_STREAMABLE:
7546       g_value_set_boolean (value, qtmux->streamable);
7547       break;
7548     default:
7549       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
7550       break;
7551   }
7552   GST_OBJECT_UNLOCK (qtmux);
7553 }
7554 
7555 static void
gst_qt_mux_subclass_class_init(GstQTMuxClass * klass)7556 gst_qt_mux_subclass_class_init (GstQTMuxClass * klass)
7557 {
7558   GObjectClass *gobject_class = (GObjectClass *) klass;
7559   GParamFlags streamable_flags;
7560   const gchar *streamable_desc;
7561   gboolean streamable;
7562 #define STREAMABLE_DESC "If set to true, the output should be as if it is to "\
7563   "be streamed and hence no indexes written or duration written."
7564 
7565   gobject_class->set_property = gst_qt_mux_subclass_set_property;
7566   gobject_class->get_property = gst_qt_mux_subclass_get_property;
7567 
7568   streamable_flags = G_PARAM_READWRITE | G_PARAM_CONSTRUCT;
7569   if (klass->format == GST_QT_MUX_FORMAT_ISML) {
7570     streamable_desc = STREAMABLE_DESC;
7571     streamable = DEFAULT_STREAMABLE;
7572   } else {
7573     streamable_desc =
7574         STREAMABLE_DESC " (DEPRECATED, only valid for fragmented MP4)";
7575     streamable_flags |= G_PARAM_DEPRECATED;
7576     streamable = FALSE;
7577   }
7578 
7579   g_object_class_install_property (gobject_class, PROP_SUBCLASS_STREAMABLE,
7580       g_param_spec_boolean ("streamable", "Streamable", streamable_desc,
7581           streamable, streamable_flags | G_PARAM_STATIC_STRINGS));
7582 }
7583 
7584 static void
gst_qt_mux_subclass_init(GstQTMux * qtmux)7585 gst_qt_mux_subclass_init (GstQTMux * qtmux)
7586 {
7587 }
7588 
7589 gboolean
gst_qt_mux_register(GstPlugin * plugin)7590 gst_qt_mux_register (GstPlugin * plugin)
7591 {
7592   GTypeInfo parent_typeinfo = {
7593     sizeof (GstQTMuxClass),
7594     (GBaseInitFunc) gst_qt_mux_base_init,
7595     NULL,
7596     (GClassInitFunc) gst_qt_mux_class_init,
7597     NULL,
7598     NULL,
7599     sizeof (GstQTMux),
7600     0,
7601     (GInstanceInitFunc) gst_qt_mux_init,
7602   };
7603   static const GInterfaceInfo tag_setter_info = {
7604     NULL, NULL, NULL
7605   };
7606   static const GInterfaceInfo tag_xmp_writer_info = {
7607     NULL, NULL, NULL
7608   };
7609   static const GInterfaceInfo preset_info = {
7610     NULL, NULL, NULL
7611   };
7612   GType parent_type;
7613   GstQTMuxFormat format;
7614   GstQTMuxClassParams *params;
7615   guint i = 0;
7616 
7617   GST_DEBUG_CATEGORY_INIT (gst_qt_mux_debug, "qtmux", 0, "QT Muxer");
7618 
7619   GST_LOG ("Registering muxers");
7620 
7621   parent_type =
7622       g_type_register_static (GST_TYPE_AGGREGATOR, "GstBaseQTMux",
7623       &parent_typeinfo, 0);
7624   g_type_add_interface_static (parent_type, GST_TYPE_TAG_SETTER,
7625       &tag_setter_info);
7626   g_type_add_interface_static (parent_type, GST_TYPE_TAG_XMP_WRITER,
7627       &tag_xmp_writer_info);
7628   g_type_add_interface_static (parent_type, GST_TYPE_PRESET, &preset_info);
7629 
7630   gst_type_mark_as_plugin_api (parent_type, 0);
7631 
7632   while (TRUE) {
7633     GType type;
7634     GTypeInfo subclass_typeinfo = {
7635       sizeof (GstQTMuxClass),
7636       NULL,
7637       NULL,
7638       (GClassInitFunc) gst_qt_mux_subclass_class_init,
7639       NULL,
7640       NULL,
7641       sizeof (GstQTMux),
7642       0,
7643       (GInstanceInitFunc) gst_qt_mux_subclass_init,
7644     };
7645     GstQTMuxFormatProp *prop;
7646     GstCaps *subtitle_caps, *caption_caps;
7647 
7648     prop = &gst_qt_mux_format_list[i];
7649     format = prop->format;
7650     if (format == GST_QT_MUX_FORMAT_NONE)
7651       break;
7652 
7653     /* create a cache for these properties */
7654     params = g_new0 (GstQTMuxClassParams, 1);
7655     params->prop = prop;
7656     params->src_caps = gst_static_caps_get (&prop->src_caps);
7657     params->video_sink_caps = gst_static_caps_get (&prop->video_sink_caps);
7658     params->audio_sink_caps = gst_static_caps_get (&prop->audio_sink_caps);
7659     subtitle_caps = gst_static_caps_get (&prop->subtitle_sink_caps);
7660     if (!gst_caps_is_equal (subtitle_caps, GST_CAPS_NONE)) {
7661       params->subtitle_sink_caps = subtitle_caps;
7662     } else {
7663       gst_caps_unref (subtitle_caps);
7664     }
7665     caption_caps = gst_static_caps_get (&prop->caption_sink_caps);
7666     if (!gst_caps_is_equal (caption_caps, GST_CAPS_NONE)) {
7667       params->caption_sink_caps = caption_caps;
7668     } else {
7669       gst_caps_unref (caption_caps);
7670     }
7671 
7672     /* create the type now */
7673     type =
7674         g_type_register_static (parent_type, prop->type_name,
7675         &subclass_typeinfo, 0);
7676     g_type_set_qdata (type, GST_QT_MUX_PARAMS_QDATA, (gpointer) params);
7677 
7678     if (!gst_element_register (plugin, prop->name, prop->rank, type))
7679       return FALSE;
7680 
7681     i++;
7682   }
7683 
7684   GST_LOG ("Finished registering muxers");
7685 
7686   /* FIXME: ideally classification tag should be added and
7687      registered in gstreamer core gsttaglist
7688    */
7689 
7690   GST_LOG ("Registering tags");
7691 
7692   gst_tag_register (GST_TAG_3GP_CLASSIFICATION, GST_TAG_FLAG_META,
7693       G_TYPE_STRING, GST_TAG_3GP_CLASSIFICATION, "content classification",
7694       gst_tag_merge_use_first);
7695 
7696   isomp4_element_init (plugin);
7697 
7698   GST_LOG ("Finished registering tags");
7699 
7700   return TRUE;
7701 }
7702 
7703 GST_ELEMENT_REGISTER_DEFINE_CUSTOM (qtmux, gst_qt_mux_register);
7704