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