1 /* Quicktime muxer plugin for GStreamer
2 * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@collabora.co.uk>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19 /*
20 * Unless otherwise indicated, Source Code is licensed under MIT license.
21 * See further explanation attached in License Statement (distributed in the file
22 * LICENSE).
23 *
24 * Permission is hereby granted, free of charge, to any person obtaining a copy of
25 * this software and associated documentation files (the "Software"), to deal in
26 * the Software without restriction, including without limitation the rights to
27 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
28 * of the Software, and to permit persons to whom the Software is furnished to do
29 * so, subject to the following conditions:
30 *
31 * The above copyright notice and this permission notice shall be included in all
32 * copies or substantial portions of the Software.
33 *
34 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
36 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
37 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
38 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
39 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
40 * SOFTWARE.
41 */
42
43 /*
44 * This module contains functions for serializing partial information from
45 * a mux in progress (by qtmux elements). This enables reconstruction of the
46 * moov box if a crash happens and thus recovering the movie file.
47 *
48 * Usage:
49 * 1) pipeline: ...yourelements ! qtmux moov-recovery-file=path.mrf ! \
50 * filesink location=moovie.mov
51 *
52 * 2) CRASH!
53 *
54 * 3) gst-launch-1.0 qtmoovrecover recovery-input=path.mrf broken-input=moovie.mov \
55 fixed-output=recovered.mov
56 *
57 * 4) (Hopefully) enjoy recovered.mov.
58 *
59 * --- Recovery file layout ---
60 * 1) Version (a guint16)
61 * 2) Prefix atom (if present)
62 * 3) ftyp atom
63 * 4) MVHD atom (without timescale/duration set)
64 * 5) moovie timescale
65 * 6) number of traks
66 * 7) list of trak atoms (stbl data is ignored, except for the stsd atom)
67 * 8) Buffers metadata (metadata that is relevant to the container)
68 * Buffers metadata are stored in the order they are added to the mdat,
69 * each entre has a fixed size and is stored in BE. booleans are stored
70 * as a single byte where 0 means false, otherwise is true.
71 * Metadata:
72 * - guint32 track_id;
73 * - guint32 nsamples;
74 * - guint32 delta;
75 * - guint32 size;
76 * - guint64 chunk_offset;
77 * - gboolean sync;
78 * - gboolean do_pts;
79 * - guint64 pts_offset; (always present, ignored if do_pts is false)
80 *
81 * The mdat file might contain ftyp and then mdat, in case this is the faststart
82 * temporary file there is no ftyp and no mdat header, only the buffers data.
83 *
84 * Notes about recovery file layout: We still don't store tags nor EDTS data.
85 *
86 * IMPORTANT: this is still at a experimental state.
87 */
88
89 #include "atomsrecovery.h"
90
91 #define MAX_CHUNK_SIZE (1024 * 1024) /* 1MB */
92
93 #define ATOMS_RECOV_OUTPUT_WRITE_ERROR(err) \
94 g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE, \
95 "Failed to write to output file: %s", g_strerror (errno))
96
97 static gboolean
atoms_recov_write_version(FILE * f)98 atoms_recov_write_version (FILE * f)
99 {
100 guint8 data[2];
101 GST_WRITE_UINT16_BE (data, ATOMS_RECOV_FILE_VERSION);
102 return fwrite (data, 2, 1, f) == 1;
103 }
104
105 static gboolean
atoms_recov_write_ftyp_info(FILE * f,AtomFTYP * ftyp,GstBuffer * prefix)106 atoms_recov_write_ftyp_info (FILE * f, AtomFTYP * ftyp, GstBuffer * prefix)
107 {
108 guint8 *data = NULL;
109 guint64 offset = 0;
110 guint64 size = 0;
111
112 if (prefix) {
113 GstMapInfo map;
114
115 if (!gst_buffer_map (prefix, &map, GST_MAP_READ)) {
116 return FALSE;
117 }
118 if (fwrite (map.data, 1, map.size, f) != map.size) {
119 gst_buffer_unmap (prefix, &map);
120 return FALSE;
121 }
122 gst_buffer_unmap (prefix, &map);
123 }
124 if (!atom_ftyp_copy_data (ftyp, &data, &size, &offset)) {
125 return FALSE;
126 }
127 if (fwrite (data, 1, offset, f) != offset) {
128 g_free (data);
129 return FALSE;
130 }
131 g_free (data);
132 return TRUE;
133 }
134
135 /*
136 * Writes important info on the 'moov' atom (non-trak related)
137 * to be able to recover the moov structure after a crash.
138 *
139 * Currently, it writes the MVHD atom.
140 */
141 static gboolean
atoms_recov_write_moov_info(FILE * f,AtomMOOV * moov)142 atoms_recov_write_moov_info (FILE * f, AtomMOOV * moov)
143 {
144 guint8 *data;
145 guint64 size;
146 guint64 offset = 0;
147 guint64 atom_size = 0;
148 gint writen = 0;
149
150 /* likely enough */
151 size = 256;
152 data = g_malloc (size);
153 atom_size = atom_mvhd_copy_data (&moov->mvhd, &data, &size, &offset);
154 if (atom_size > 0)
155 writen = fwrite (data, 1, atom_size, f);
156 g_free (data);
157 return atom_size > 0 && writen == atom_size;
158 }
159
160 /*
161 * Writes the number of traks to the file.
162 * This simply writes a guint32 in BE.
163 */
164 static gboolean
atoms_recov_write_traks_number(FILE * f,guint32 traks)165 atoms_recov_write_traks_number (FILE * f, guint32 traks)
166 {
167 guint8 data[4];
168 GST_WRITE_UINT32_BE (data, traks);
169 return fwrite (data, 4, 1, f) == 1;
170 }
171
172 /*
173 * Writes the moov's timescale to the file
174 * This simply writes a guint32 in BE.
175 */
176 static gboolean
atoms_recov_write_moov_timescale(FILE * f,guint32 timescale)177 atoms_recov_write_moov_timescale (FILE * f, guint32 timescale)
178 {
179 guint8 data[4];
180 GST_WRITE_UINT32_BE (data, timescale);
181 return fwrite (data, 4, 1, f) == 1;
182 }
183
184 /*
185 * Writes the trak atom to the file.
186 */
187 gboolean
atoms_recov_write_trak_info(FILE * f,AtomTRAK * trak)188 atoms_recov_write_trak_info (FILE * f, AtomTRAK * trak)
189 {
190 guint8 *data;
191 guint64 size;
192 guint64 offset = 0;
193 guint64 atom_size = 0;
194 gint writen = 0;
195
196 /* buffer is realloced to a larger size if needed */
197 size = 4 * 1024;
198 data = g_malloc (size);
199 atom_size = atom_trak_copy_data (trak, &data, &size, &offset);
200 if (atom_size > 0)
201 writen = fwrite (data, atom_size, 1, f);
202 g_free (data);
203 return atom_size > 0 && writen == atom_size;
204 }
205
206 gboolean
atoms_recov_write_trak_samples(FILE * f,AtomTRAK * trak,guint32 nsamples,guint32 delta,guint32 size,guint64 chunk_offset,gboolean sync,gboolean do_pts,gint64 pts_offset)207 atoms_recov_write_trak_samples (FILE * f, AtomTRAK * trak, guint32 nsamples,
208 guint32 delta, guint32 size, guint64 chunk_offset, gboolean sync,
209 gboolean do_pts, gint64 pts_offset)
210 {
211 guint8 data[TRAK_BUFFER_ENTRY_INFO_SIZE];
212 /*
213 * We have to write a TrakBufferEntryInfo
214 */
215 GST_WRITE_UINT32_BE (data + 0, trak->tkhd.track_ID);
216 GST_WRITE_UINT32_BE (data + 4, nsamples);
217 GST_WRITE_UINT32_BE (data + 8, delta);
218 GST_WRITE_UINT32_BE (data + 12, size);
219 GST_WRITE_UINT64_BE (data + 16, chunk_offset);
220 if (sync)
221 GST_WRITE_UINT8 (data + 24, 1);
222 else
223 GST_WRITE_UINT8 (data + 24, 0);
224 if (do_pts) {
225 GST_WRITE_UINT8 (data + 25, 1);
226 GST_WRITE_UINT64_BE (data + 26, pts_offset);
227 } else {
228 GST_WRITE_UINT8 (data + 25, 0);
229 GST_WRITE_UINT64_BE (data + 26, 0);
230 }
231
232 return fwrite (data, 1, TRAK_BUFFER_ENTRY_INFO_SIZE, f) ==
233 TRAK_BUFFER_ENTRY_INFO_SIZE;
234 }
235
236 gboolean
atoms_recov_write_headers(FILE * f,AtomFTYP * ftyp,GstBuffer * prefix,AtomMOOV * moov,guint32 timescale,guint32 traks_number)237 atoms_recov_write_headers (FILE * f, AtomFTYP * ftyp, GstBuffer * prefix,
238 AtomMOOV * moov, guint32 timescale, guint32 traks_number)
239 {
240 if (!atoms_recov_write_version (f)) {
241 return FALSE;
242 }
243
244 if (!atoms_recov_write_ftyp_info (f, ftyp, prefix)) {
245 return FALSE;
246 }
247
248 if (!atoms_recov_write_moov_info (f, moov)) {
249 return FALSE;
250 }
251
252 if (!atoms_recov_write_moov_timescale (f, timescale)) {
253 return FALSE;
254 }
255
256 if (!atoms_recov_write_traks_number (f, traks_number)) {
257 return FALSE;
258 }
259
260 return TRUE;
261 }
262
263 static gboolean
read_atom_header(FILE * f,guint32 * fourcc,guint32 * size)264 read_atom_header (FILE * f, guint32 * fourcc, guint32 * size)
265 {
266 guint8 aux[8];
267
268 if (fread (aux, 1, 8, f) != 8)
269 return FALSE;
270 *size = GST_READ_UINT32_BE (aux);
271 *fourcc = GST_READ_UINT32_LE (aux + 4);
272 return TRUE;
273 }
274
275 static gboolean
moov_recov_file_parse_prefix(MoovRecovFile * moovrf)276 moov_recov_file_parse_prefix (MoovRecovFile * moovrf)
277 {
278 guint32 fourcc;
279 guint32 size;
280 guint32 total_size = 0;
281 if (fseek (moovrf->file, 2, SEEK_SET) != 0)
282 return FALSE;
283 if (!read_atom_header (moovrf->file, &fourcc, &size)) {
284 return FALSE;
285 }
286
287 if (fourcc != FOURCC_ftyp) {
288 /* we might have a prefix here */
289 if (fseek (moovrf->file, size - 8, SEEK_CUR) != 0)
290 return FALSE;
291
292 total_size += size;
293
294 /* now read the ftyp */
295 if (!read_atom_header (moovrf->file, &fourcc, &size))
296 return FALSE;
297 }
298
299 /* this has to be the ftyp */
300 if (fourcc != FOURCC_ftyp)
301 return FALSE;
302 total_size += size;
303 moovrf->prefix_size = total_size;
304 return fseek (moovrf->file, size - 8, SEEK_CUR) == 0;
305 }
306
307 static gboolean
moov_recov_file_parse_mvhd(MoovRecovFile * moovrf)308 moov_recov_file_parse_mvhd (MoovRecovFile * moovrf)
309 {
310 guint32 fourcc;
311 guint32 size;
312 if (!read_atom_header (moovrf->file, &fourcc, &size)) {
313 return FALSE;
314 }
315 /* check for sanity */
316 if (fourcc != FOURCC_mvhd)
317 return FALSE;
318
319 moovrf->mvhd_size = size;
320 moovrf->mvhd_pos = ftell (moovrf->file) - 8;
321
322 /* skip the remaining of the mvhd in the file */
323 return fseek (moovrf->file, size - 8, SEEK_CUR) == 0;
324 }
325
326 static gboolean
mdat_recov_file_parse_mdat_start(MdatRecovFile * mdatrf)327 mdat_recov_file_parse_mdat_start (MdatRecovFile * mdatrf)
328 {
329 guint32 fourcc, size;
330
331 if (!read_atom_header (mdatrf->file, &fourcc, &size)) {
332 return FALSE;
333 }
334 if (size == 1) {
335 mdatrf->mdat_header_size = 16;
336 mdatrf->mdat_size = 16;
337 } else {
338 mdatrf->mdat_header_size = 8;
339 mdatrf->mdat_size = 8;
340 }
341 mdatrf->mdat_start = ftell (mdatrf->file) - 8;
342
343 return fourcc == FOURCC_mdat;
344 }
345
346 static gboolean
mdat_recov_file_find_mdat(FILE * file,GError ** err)347 mdat_recov_file_find_mdat (FILE * file, GError ** err)
348 {
349 guint32 fourcc = 0, size = 0;
350 gboolean failure = FALSE;
351 while (fourcc != FOURCC_mdat && !failure) {
352 if (!read_atom_header (file, &fourcc, &size)) {
353 goto parse_error;
354 }
355 switch (fourcc) {
356 /* skip these atoms */
357 case FOURCC_ftyp:
358 case FOURCC_free:
359 case FOURCC_udta:
360 if (fseek (file, size - 8, SEEK_CUR) != 0) {
361 goto file_seek_error;
362 }
363 break;
364 case FOURCC_mdat:
365 break;
366 default:
367 GST_ERROR ("Unexpected atom in headers %" GST_FOURCC_FORMAT,
368 GST_FOURCC_ARGS (fourcc));
369 failure = TRUE;
370 break;
371 }
372 }
373
374 if (!failure) {
375 /* Reverse to mdat start */
376 if (fseek (file, -8, SEEK_CUR) != 0)
377 goto file_seek_error;
378 }
379
380 return !failure;
381
382 parse_error:
383 g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
384 "Failed to parse atom");
385 return FALSE;
386
387 file_seek_error:
388 g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
389 "Failed to seek to start of the file");
390 return FALSE;
391
392 }
393
394 MdatRecovFile *
mdat_recov_file_create(FILE * file,gboolean datafile,GError ** err)395 mdat_recov_file_create (FILE * file, gboolean datafile, GError ** err)
396 {
397 MdatRecovFile *mrf = g_new0 (MdatRecovFile, 1);
398
399 g_return_val_if_fail (file != NULL, NULL);
400
401 mrf->file = file;
402 mrf->rawfile = datafile;
403
404 /* get the file/data length */
405 if (fseek (file, 0, SEEK_END) != 0)
406 goto file_length_error;
407 /* still needs to deduce the mdat header and ftyp size */
408 mrf->data_size = ftell (file);
409 if (mrf->data_size == -1L)
410 goto file_length_error;
411
412 if (fseek (file, 0, SEEK_SET) != 0)
413 goto file_seek_error;
414
415 if (datafile) {
416 /* this file contains no atoms, only raw data to be placed on the mdat
417 * this happens when faststart mode is used */
418 mrf->mdat_start = 0;
419 mrf->mdat_header_size = 16;
420 mrf->mdat_size = 16;
421 return mrf;
422 }
423
424 if (!mdat_recov_file_find_mdat (file, err)) {
425 goto fail;
426 }
427
428 /* we don't parse this if we have a tmpdatafile */
429 if (!mdat_recov_file_parse_mdat_start (mrf)) {
430 g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
431 "Error while parsing mdat atom");
432 goto fail;
433 }
434
435 return mrf;
436
437 file_seek_error:
438 g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
439 "Failed to seek to start of the file");
440 goto fail;
441
442 file_length_error:
443 g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
444 "Failed to determine file size");
445 goto fail;
446
447 fail:
448 mdat_recov_file_free (mrf);
449 return NULL;
450 }
451
452 void
mdat_recov_file_free(MdatRecovFile * mrf)453 mdat_recov_file_free (MdatRecovFile * mrf)
454 {
455 fclose (mrf->file);
456 g_free (mrf);
457 }
458
459 static gboolean
moov_recov_parse_num_traks(MoovRecovFile * moovrf)460 moov_recov_parse_num_traks (MoovRecovFile * moovrf)
461 {
462 guint8 traks[4];
463 if (fread (traks, 1, 4, moovrf->file) != 4)
464 return FALSE;
465 moovrf->num_traks = GST_READ_UINT32_BE (traks);
466 return TRUE;
467 }
468
469 static gboolean
moov_recov_parse_moov_timescale(MoovRecovFile * moovrf)470 moov_recov_parse_moov_timescale (MoovRecovFile * moovrf)
471 {
472 guint8 ts[4];
473 if (fread (ts, 1, 4, moovrf->file) != 4)
474 return FALSE;
475 moovrf->timescale = GST_READ_UINT32_BE (ts);
476 return TRUE;
477 }
478
479 static gboolean
skip_atom(MoovRecovFile * moovrf,guint32 expected_fourcc)480 skip_atom (MoovRecovFile * moovrf, guint32 expected_fourcc)
481 {
482 guint32 size;
483 guint32 fourcc;
484
485 if (!read_atom_header (moovrf->file, &fourcc, &size))
486 return FALSE;
487 if (fourcc != expected_fourcc)
488 return FALSE;
489
490 return (fseek (moovrf->file, size - 8, SEEK_CUR) == 0);
491 }
492
493 static gboolean
moov_recov_parse_tkhd(MoovRecovFile * moovrf,TrakRecovData * trakrd)494 moov_recov_parse_tkhd (MoovRecovFile * moovrf, TrakRecovData * trakrd)
495 {
496 guint32 size;
497 guint32 fourcc;
498 guint8 data[4];
499
500 /* make sure we are on a tkhd atom */
501 if (!read_atom_header (moovrf->file, &fourcc, &size))
502 return FALSE;
503 if (fourcc != FOURCC_tkhd)
504 return FALSE;
505
506 trakrd->tkhd_file_offset = ftell (moovrf->file) - 8;
507
508 /* move 8 bytes forward to the trak_id pos */
509 if (fseek (moovrf->file, 12, SEEK_CUR) != 0)
510 return FALSE;
511 if (fread (data, 1, 4, moovrf->file) != 4)
512 return FALSE;
513
514 /* advance the rest of tkhd */
515 if (fseek (moovrf->file, 68, SEEK_CUR) != 0)
516 return FALSE;
517
518 trakrd->trak_id = GST_READ_UINT32_BE (data);
519 return TRUE;
520 }
521
522 static gboolean
moov_recov_parse_stbl(MoovRecovFile * moovrf,TrakRecovData * trakrd)523 moov_recov_parse_stbl (MoovRecovFile * moovrf, TrakRecovData * trakrd)
524 {
525 guint32 size;
526 guint32 fourcc;
527 guint32 auxsize;
528
529 if (!read_atom_header (moovrf->file, &fourcc, &size))
530 return FALSE;
531 if (fourcc != FOURCC_stbl)
532 return FALSE;
533
534 trakrd->stbl_file_offset = ftell (moovrf->file) - 8;
535 trakrd->stbl_size = size;
536
537 /* skip the stsd */
538 if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
539 return FALSE;
540 if (fourcc != FOURCC_stsd)
541 return FALSE;
542 if (fseek (moovrf->file, auxsize - 8, SEEK_CUR) != 0)
543 return FALSE;
544
545 trakrd->stsd_size = auxsize;
546 trakrd->post_stsd_offset = ftell (moovrf->file);
547
548 /* as this is the last atom we parse, we don't skip forward */
549
550 return TRUE;
551 }
552
553 static gboolean
moov_recov_parse_minf(MoovRecovFile * moovrf,TrakRecovData * trakrd)554 moov_recov_parse_minf (MoovRecovFile * moovrf, TrakRecovData * trakrd)
555 {
556 guint32 size;
557 guint32 fourcc;
558 guint32 auxsize;
559
560 if (!read_atom_header (moovrf->file, &fourcc, &size))
561 return FALSE;
562 if (fourcc != FOURCC_minf)
563 return FALSE;
564
565 trakrd->minf_file_offset = ftell (moovrf->file) - 8;
566 trakrd->minf_size = size;
567
568 /* skip either of vmhd, smhd, hmhd that might follow */
569 if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
570 return FALSE;
571 if (fourcc != FOURCC_vmhd && fourcc != FOURCC_smhd && fourcc != FOURCC_hmhd &&
572 fourcc != FOURCC_gmhd)
573 return FALSE;
574 if (fseek (moovrf->file, auxsize - 8, SEEK_CUR))
575 return FALSE;
576
577 /* skip a possible hdlr and the following dinf */
578 if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
579 return FALSE;
580 if (fourcc == FOURCC_hdlr) {
581 if (fseek (moovrf->file, auxsize - 8, SEEK_CUR))
582 return FALSE;
583 if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
584 return FALSE;
585 }
586 if (fourcc != FOURCC_dinf)
587 return FALSE;
588 if (fseek (moovrf->file, auxsize - 8, SEEK_CUR))
589 return FALSE;
590
591 /* now we are ready to read the stbl */
592 if (!moov_recov_parse_stbl (moovrf, trakrd))
593 return FALSE;
594
595 return TRUE;
596 }
597
598 static gboolean
moov_recov_parse_mdhd(MoovRecovFile * moovrf,TrakRecovData * trakrd)599 moov_recov_parse_mdhd (MoovRecovFile * moovrf, TrakRecovData * trakrd)
600 {
601 guint32 size;
602 guint32 fourcc;
603 guint8 data[4];
604
605 /* make sure we are on a tkhd atom */
606 if (!read_atom_header (moovrf->file, &fourcc, &size))
607 return FALSE;
608 if (fourcc != FOURCC_mdhd)
609 return FALSE;
610
611 trakrd->mdhd_file_offset = ftell (moovrf->file) - 8;
612
613 /* get the timescale */
614 if (fseek (moovrf->file, 12, SEEK_CUR) != 0)
615 return FALSE;
616 if (fread (data, 1, 4, moovrf->file) != 4)
617 return FALSE;
618 trakrd->timescale = GST_READ_UINT32_BE (data);
619 if (fseek (moovrf->file, 8, SEEK_CUR) != 0)
620 return FALSE;
621 return TRUE;
622 }
623
624 static gboolean
moov_recov_parse_mdia(MoovRecovFile * moovrf,TrakRecovData * trakrd)625 moov_recov_parse_mdia (MoovRecovFile * moovrf, TrakRecovData * trakrd)
626 {
627 guint32 size;
628 guint32 fourcc;
629
630 /* make sure we are on a tkhd atom */
631 if (!read_atom_header (moovrf->file, &fourcc, &size))
632 return FALSE;
633 if (fourcc != FOURCC_mdia)
634 return FALSE;
635
636 trakrd->mdia_file_offset = ftell (moovrf->file) - 8;
637 trakrd->mdia_size = size;
638
639 if (!moov_recov_parse_mdhd (moovrf, trakrd))
640 return FALSE;
641
642 if (!skip_atom (moovrf, FOURCC_hdlr))
643 return FALSE;
644 if (!moov_recov_parse_minf (moovrf, trakrd))
645 return FALSE;
646 return TRUE;
647 }
648
649 static gboolean
moov_recov_parse_trak(MoovRecovFile * moovrf,TrakRecovData * trakrd)650 moov_recov_parse_trak (MoovRecovFile * moovrf, TrakRecovData * trakrd)
651 {
652 guint64 offset;
653 guint32 size;
654 guint32 fourcc;
655
656 offset = ftell (moovrf->file);
657 if (offset == -1) {
658 return FALSE;
659 }
660
661 /* make sure we are on a trak atom */
662 if (!read_atom_header (moovrf->file, &fourcc, &size)) {
663 return FALSE;
664 }
665 if (fourcc != FOURCC_trak) {
666 return FALSE;
667 }
668 trakrd->trak_size = size;
669
670 /* now we should have a trak header 'tkhd' */
671 if (!moov_recov_parse_tkhd (moovrf, trakrd))
672 return FALSE;
673
674 /* FIXME add edts handling here and in qtmux, as this is only detected
675 * after buffers start flowing */
676
677 if (!moov_recov_parse_mdia (moovrf, trakrd))
678 return FALSE;
679
680 if (fseek (moovrf->file,
681 (long int) trakrd->mdia_file_offset + trakrd->mdia_size,
682 SEEK_SET) != 0)
683 return FALSE;
684
685 trakrd->extra_atoms_offset = ftell (moovrf->file);
686 trakrd->extra_atoms_size = size - (trakrd->extra_atoms_offset - offset);
687
688 trakrd->file_offset = offset;
689 /* position after the trak */
690 return fseek (moovrf->file, (long int) offset + size, SEEK_SET) == 0;
691 }
692
693 MoovRecovFile *
moov_recov_file_create(FILE * file,GError ** err)694 moov_recov_file_create (FILE * file, GError ** err)
695 {
696 gint i;
697 MoovRecovFile *moovrf = g_new0 (MoovRecovFile, 1);
698
699 g_return_val_if_fail (file != NULL, NULL);
700
701 moovrf->file = file;
702
703 /* look for ftyp and prefix at the start */
704 if (!moov_recov_file_parse_prefix (moovrf)) {
705 g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
706 "Error while parsing prefix atoms");
707 goto fail;
708 }
709
710 /* parse the mvhd */
711 if (!moov_recov_file_parse_mvhd (moovrf)) {
712 g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
713 "Error while parsing mvhd atom");
714 goto fail;
715 }
716
717 if (!moov_recov_parse_moov_timescale (moovrf)) {
718 g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
719 "Error while parsing timescale");
720 goto fail;
721 }
722 if (!moov_recov_parse_num_traks (moovrf)) {
723 g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
724 "Error while parsing parsing number of traks");
725 goto fail;
726 }
727
728 /* sanity check */
729 if (moovrf->num_traks > 1024) {
730 g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
731 "Unsupported number of traks");
732 goto fail;
733 }
734
735 /* init the traks */
736 moovrf->traks_rd = g_new0 (TrakRecovData, moovrf->num_traks);
737 for (i = 0; i < moovrf->num_traks; i++) {
738 atom_stbl_init (&(moovrf->traks_rd[i].stbl));
739 }
740 for (i = 0; i < moovrf->num_traks; i++) {
741 if (!moov_recov_parse_trak (moovrf, &(moovrf->traks_rd[i]))) {
742 g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
743 "Error while parsing trak atom");
744 goto fail;
745 }
746 }
747
748 return moovrf;
749
750 fail:
751 moov_recov_file_free (moovrf);
752 return NULL;
753 }
754
755 void
moov_recov_file_free(MoovRecovFile * moovrf)756 moov_recov_file_free (MoovRecovFile * moovrf)
757 {
758 gint i;
759 fclose (moovrf->file);
760 if (moovrf->traks_rd) {
761 for (i = 0; i < moovrf->num_traks; i++) {
762 atom_stbl_clear (&(moovrf->traks_rd[i].stbl));
763 }
764 g_free (moovrf->traks_rd);
765 }
766 g_free (moovrf);
767 }
768
769 static gboolean
moov_recov_parse_buffer_entry(MoovRecovFile * moovrf,TrakBufferEntryInfo * b)770 moov_recov_parse_buffer_entry (MoovRecovFile * moovrf, TrakBufferEntryInfo * b)
771 {
772 guint8 data[TRAK_BUFFER_ENTRY_INFO_SIZE];
773 gint read;
774
775 read = fread (data, 1, TRAK_BUFFER_ENTRY_INFO_SIZE, moovrf->file);
776 if (read != TRAK_BUFFER_ENTRY_INFO_SIZE)
777 return FALSE;
778
779 b->track_id = GST_READ_UINT32_BE (data);
780 b->nsamples = GST_READ_UINT32_BE (data + 4);
781 b->delta = GST_READ_UINT32_BE (data + 8);
782 b->size = GST_READ_UINT32_BE (data + 12);
783 b->chunk_offset = GST_READ_UINT64_BE (data + 16);
784 b->sync = data[24] != 0;
785 b->do_pts = data[25] != 0;
786 b->pts_offset = GST_READ_UINT64_BE (data + 26);
787 return TRUE;
788 }
789
790 static gboolean
mdat_recov_add_sample(MdatRecovFile * mdatrf,guint32 size)791 mdat_recov_add_sample (MdatRecovFile * mdatrf, guint32 size)
792 {
793 /* test if this data exists */
794 if (mdatrf->mdat_size - mdatrf->mdat_header_size + size > mdatrf->data_size)
795 return FALSE;
796
797 mdatrf->mdat_size += size;
798 return TRUE;
799 }
800
801 static TrakRecovData *
moov_recov_get_trak(MoovRecovFile * moovrf,guint32 id)802 moov_recov_get_trak (MoovRecovFile * moovrf, guint32 id)
803 {
804 gint i;
805 for (i = 0; i < moovrf->num_traks; i++) {
806 if (moovrf->traks_rd[i].trak_id == id)
807 return &(moovrf->traks_rd[i]);
808 }
809 return NULL;
810 }
811
812 static void
trak_recov_data_add_sample(TrakRecovData * trak,TrakBufferEntryInfo * b)813 trak_recov_data_add_sample (TrakRecovData * trak, TrakBufferEntryInfo * b)
814 {
815 trak->duration += b->nsamples * b->delta;
816 atom_stbl_add_samples (&trak->stbl, b->nsamples, b->delta, b->size,
817 b->chunk_offset, b->sync, b->pts_offset);
818 }
819
820 /*
821 * Parses the buffer entries in the MoovRecovFile and matches the inputs
822 * with the data in the MdatRecovFile. Whenever a buffer entry of that
823 * represents 'x' bytes of data, the same amount of data is 'validated' in
824 * the MdatRecovFile and will be inluded in the generated moovie file.
825 */
826 gboolean
moov_recov_parse_buffers(MoovRecovFile * moovrf,MdatRecovFile * mdatrf,GError ** err)827 moov_recov_parse_buffers (MoovRecovFile * moovrf, MdatRecovFile * mdatrf,
828 GError ** err)
829 {
830 TrakBufferEntryInfo entry;
831 TrakRecovData *trak;
832
833 /* we assume both moovrf and mdatrf are at the starting points of their
834 * data reading */
835 while (moov_recov_parse_buffer_entry (moovrf, &entry)) {
836 /* be sure we still have this data in mdat */
837 trak = moov_recov_get_trak (moovrf, entry.track_id);
838 if (trak == NULL) {
839 g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
840 "Invalid trak id found in buffer entry");
841 return FALSE;
842 }
843 if (!mdat_recov_add_sample (mdatrf, entry.size))
844 break;
845 trak_recov_data_add_sample (trak, &entry);
846 }
847 return TRUE;
848 }
849
850 static guint32
trak_recov_data_get_trak_atom_size(TrakRecovData * trak)851 trak_recov_data_get_trak_atom_size (TrakRecovData * trak)
852 {
853 AtomSTBL *stbl = &trak->stbl;
854 guint64 offset;
855
856 /* write out our stbl child atoms */
857 offset = 0;
858
859 if (!atom_stts_copy_data (&stbl->stts, NULL, NULL, &offset)) {
860 goto fail;
861 }
862 if (atom_array_get_len (&stbl->stss.entries) > 0) {
863 if (!atom_stss_copy_data (&stbl->stss, NULL, NULL, &offset)) {
864 goto fail;
865 }
866 }
867 if (!atom_stsc_copy_data (&stbl->stsc, NULL, NULL, &offset)) {
868 goto fail;
869 }
870 if (!atom_stsz_copy_data (&stbl->stsz, NULL, NULL, &offset)) {
871 goto fail;
872 }
873 if (stbl->ctts) {
874 if (!atom_ctts_copy_data (stbl->ctts, NULL, NULL, &offset)) {
875 goto fail;
876 }
877 }
878 if (!atom_stco64_copy_data (&stbl->stco64, NULL, NULL, &offset)) {
879 goto fail;
880 }
881
882 return trak->trak_size + ((trak->stsd_size + offset + 8) - trak->stbl_size);
883
884 fail:
885 return 0;
886 }
887
888 static guint8 *
moov_recov_get_stbl_children_data(MoovRecovFile * moovrf,TrakRecovData * trak,guint64 * p_size)889 moov_recov_get_stbl_children_data (MoovRecovFile * moovrf, TrakRecovData * trak,
890 guint64 * p_size)
891 {
892 AtomSTBL *stbl = &trak->stbl;
893 guint8 *buffer;
894 guint64 size;
895 guint64 offset;
896
897 /* write out our stbl child atoms
898 *
899 * Use 1MB as a starting size, *_copy_data functions
900 * will grow the buffer if needed.
901 */
902 size = 1024 * 1024;
903 buffer = g_malloc0 (size);
904 offset = 0;
905
906 if (!atom_stts_copy_data (&stbl->stts, &buffer, &size, &offset)) {
907 goto fail;
908 }
909 if (atom_array_get_len (&stbl->stss.entries) > 0) {
910 if (!atom_stss_copy_data (&stbl->stss, &buffer, &size, &offset)) {
911 goto fail;
912 }
913 }
914 if (!atom_stsc_copy_data (&stbl->stsc, &buffer, &size, &offset)) {
915 goto fail;
916 }
917 if (!atom_stsz_copy_data (&stbl->stsz, &buffer, &size, &offset)) {
918 goto fail;
919 }
920 if (stbl->ctts) {
921 if (!atom_ctts_copy_data (stbl->ctts, &buffer, &size, &offset)) {
922 goto fail;
923 }
924 }
925 if (!atom_stco64_copy_data (&stbl->stco64, &buffer, &size, &offset)) {
926 goto fail;
927 }
928 *p_size = offset;
929 return buffer;
930
931 fail:
932 g_free (buffer);
933 return NULL;
934 }
935
936 static gboolean
copy_data_from_file_to_file(FILE * from,guint position,guint size,FILE * to,GError ** err)937 copy_data_from_file_to_file (FILE * from, guint position, guint size, FILE * to,
938 GError ** err)
939 {
940 guint8 *data = NULL;
941
942 if (fseek (from, position, SEEK_SET) != 0)
943 goto fail;
944 data = g_malloc (size);
945 if (fread (data, 1, size, from) != size) {
946 goto fail;
947 }
948 if (fwrite (data, 1, size, to) != size) {
949 ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
950 goto fail;
951 }
952
953 g_free (data);
954 return TRUE;
955
956 fail:
957 g_free (data);
958 return FALSE;
959 }
960
961 gboolean
moov_recov_write_file(MoovRecovFile * moovrf,MdatRecovFile * mdatrf,FILE * outf,GError ** err,GError ** warn)962 moov_recov_write_file (MoovRecovFile * moovrf, MdatRecovFile * mdatrf,
963 FILE * outf, GError ** err, GError ** warn)
964 {
965 guint8 auxdata[16];
966 guint8 *data = NULL;
967 guint8 *prefix_data = NULL;
968 guint8 *mvhd_data = NULL;
969 guint8 *trak_data = NULL;
970 guint32 moov_size = 0;
971 gint i;
972 guint64 stbl_children_size = 0;
973 guint8 *stbl_children = NULL;
974 guint32 longest_duration = 0;
975 guint16 version;
976 guint remaining;
977
978 /* check the version */
979 if (fseek (moovrf->file, 0, SEEK_SET) != 0) {
980 g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
981 "Failed to seek to the start of the moov recovery file");
982 goto fail;
983 }
984 if (fread (auxdata, 1, 2, moovrf->file) != 2) {
985 g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
986 "Failed to read version from file");
987 }
988
989 version = GST_READ_UINT16_BE (auxdata);
990 if (version != ATOMS_RECOV_FILE_VERSION) {
991 g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_VERSION,
992 "Input file version (%u) is not supported in this version (%u)",
993 version, ATOMS_RECOV_FILE_VERSION);
994 return FALSE;
995 }
996
997 /* write the ftyp */
998 prefix_data = g_malloc (moovrf->prefix_size);
999 if (fread (prefix_data, 1, moovrf->prefix_size,
1000 moovrf->file) != moovrf->prefix_size) {
1001 g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
1002 "Failed to read the ftyp atom from file");
1003 goto fail;
1004 }
1005 if (fwrite (prefix_data, 1, moovrf->prefix_size, outf) != moovrf->prefix_size) {
1006 ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1007 goto fail;
1008 }
1009 g_free (prefix_data);
1010 prefix_data = NULL;
1011
1012 /* need to calculate the moov size beforehand to add the offset to
1013 * chunk offset entries */
1014 moov_size += moovrf->mvhd_size + 8; /* mvhd + moov size + fourcc */
1015 for (i = 0; i < moovrf->num_traks; i++) {
1016 TrakRecovData *trak = &(moovrf->traks_rd[i]);
1017 guint32 duration; /* in moov's timescale */
1018 guint32 trak_size;
1019
1020 /* convert trak duration to moov's duration */
1021 duration = gst_util_uint64_scale_round (trak->duration, moovrf->timescale,
1022 trak->timescale);
1023
1024 if (duration > longest_duration)
1025 longest_duration = duration;
1026 trak_size = trak_recov_data_get_trak_atom_size (trak);
1027 if (trak_size == 0) {
1028 g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_GENERIC,
1029 "Failed to estimate trak atom size");
1030 goto fail;
1031 }
1032 moov_size += trak_size;
1033 }
1034
1035 /* add chunks offsets */
1036 for (i = 0; i < moovrf->num_traks; i++) {
1037 TrakRecovData *trak = &(moovrf->traks_rd[i]);
1038 /* 8 or 16 for the mdat header */
1039 gint64 offset = moov_size + ftell (outf) + mdatrf->mdat_header_size;
1040 atom_stco64_chunks_set_offset (&trak->stbl.stco64, offset);
1041 }
1042
1043 /* write the moov */
1044 GST_WRITE_UINT32_BE (auxdata, moov_size);
1045 GST_WRITE_UINT32_LE (auxdata + 4, FOURCC_moov);
1046 if (fwrite (auxdata, 1, 8, outf) != 8) {
1047 ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1048 goto fail;
1049 }
1050
1051 /* write the mvhd */
1052 mvhd_data = g_malloc (moovrf->mvhd_size);
1053 if (fseek (moovrf->file, moovrf->mvhd_pos, SEEK_SET) != 0)
1054 goto fail;
1055 if (fread (mvhd_data, 1, moovrf->mvhd_size,
1056 moovrf->file) != moovrf->mvhd_size)
1057 goto fail;
1058 GST_WRITE_UINT32_BE (mvhd_data + 20, moovrf->timescale);
1059 GST_WRITE_UINT32_BE (mvhd_data + 24, longest_duration);
1060 if (fwrite (mvhd_data, 1, moovrf->mvhd_size, outf) != moovrf->mvhd_size) {
1061 ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1062 goto fail;
1063 }
1064 g_free (mvhd_data);
1065 mvhd_data = NULL;
1066
1067 /* write the traks, this is the tough part because we need to update:
1068 * - stbl atom
1069 * - sizes of atoms from stbl to trak
1070 * - trak duration
1071 */
1072 for (i = 0; i < moovrf->num_traks; i++) {
1073 TrakRecovData *trak = &(moovrf->traks_rd[i]);
1074 guint trak_data_size;
1075 guint32 stbl_new_size;
1076 guint32 minf_new_size;
1077 guint32 mdia_new_size;
1078 guint32 trak_new_size;
1079 guint32 size_diff;
1080 guint32 duration; /* in moov's timescale */
1081
1082 /* convert trak duration to moov's duration */
1083 duration = gst_util_uint64_scale_round (trak->duration, moovrf->timescale,
1084 trak->timescale);
1085
1086 stbl_children = moov_recov_get_stbl_children_data (moovrf, trak,
1087 &stbl_children_size);
1088 if (stbl_children == NULL)
1089 goto fail;
1090
1091 /* calc the new size of the atoms from stbl to trak in the atoms tree */
1092 stbl_new_size = trak->stsd_size + stbl_children_size + 8;
1093 size_diff = stbl_new_size - trak->stbl_size;
1094 minf_new_size = trak->minf_size + size_diff;
1095 mdia_new_size = trak->mdia_size + size_diff;
1096 trak_new_size = trak->trak_size + size_diff;
1097
1098 if (fseek (moovrf->file, trak->file_offset, SEEK_SET) != 0)
1099 goto fail;
1100 trak_data_size = trak->post_stsd_offset - trak->file_offset;
1101 trak_data = g_malloc (trak_data_size);
1102 if (fread (trak_data, 1, trak_data_size, moovrf->file) != trak_data_size) {
1103 goto fail;
1104 }
1105 /* update the size values in those read atoms before writing */
1106 GST_WRITE_UINT32_BE (trak_data, trak_new_size);
1107 GST_WRITE_UINT32_BE (trak_data + (trak->mdia_file_offset -
1108 trak->file_offset), mdia_new_size);
1109 GST_WRITE_UINT32_BE (trak_data + (trak->minf_file_offset -
1110 trak->file_offset), minf_new_size);
1111 GST_WRITE_UINT32_BE (trak_data + (trak->stbl_file_offset -
1112 trak->file_offset), stbl_new_size);
1113
1114 /* update duration values in tkhd and mdhd */
1115 GST_WRITE_UINT32_BE (trak_data + (trak->tkhd_file_offset -
1116 trak->file_offset) + 28, duration);
1117 GST_WRITE_UINT32_BE (trak_data + (trak->mdhd_file_offset -
1118 trak->file_offset) + 24, trak->duration);
1119
1120 if (fwrite (trak_data, 1, trak_data_size, outf) != trak_data_size) {
1121 ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1122 goto fail;
1123 }
1124 if (fwrite (stbl_children, 1, stbl_children_size, outf) !=
1125 stbl_children_size) {
1126 ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1127 goto fail;
1128 }
1129
1130 g_free (trak_data);
1131 trak_data = NULL;
1132 g_free (stbl_children);
1133 stbl_children = NULL;
1134
1135 /* Copy the extra atoms after 'minf' */
1136 if (!copy_data_from_file_to_file (moovrf->file, trak->extra_atoms_offset,
1137 trak->extra_atoms_size, outf, err))
1138 goto fail;
1139 }
1140
1141 /* write the mdat */
1142 /* write the header first */
1143 if (mdatrf->mdat_header_size == 16) {
1144 GST_WRITE_UINT32_BE (auxdata, 1);
1145 GST_WRITE_UINT32_LE (auxdata + 4, FOURCC_mdat);
1146 GST_WRITE_UINT64_BE (auxdata + 8, mdatrf->mdat_size);
1147 } else if (mdatrf->mdat_header_size == 8) {
1148 GST_WRITE_UINT32_BE (auxdata, mdatrf->mdat_size);
1149 GST_WRITE_UINT32_LE (auxdata + 4, FOURCC_mdat);
1150 } else {
1151 GST_ERROR ("Unexpected atom size: %u", mdatrf->mdat_header_size);
1152 g_assert_not_reached ();
1153 goto fail;
1154 }
1155
1156 if (fwrite (auxdata, 1, mdatrf->mdat_header_size,
1157 outf) != mdatrf->mdat_header_size) {
1158 ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1159 goto fail;
1160 }
1161
1162 /* now read the mdat data and output to the file */
1163 if (fseek (mdatrf->file, mdatrf->mdat_start +
1164 (mdatrf->rawfile ? 0 : mdatrf->mdat_header_size), SEEK_SET) != 0)
1165 goto fail;
1166
1167 remaining = mdatrf->mdat_size - mdatrf->mdat_header_size;
1168 data = g_malloc (MAX_CHUNK_SIZE);
1169 while (!feof (mdatrf->file) && remaining > 0) {
1170 gint read, write, readsize;
1171
1172 readsize = MIN (MAX_CHUNK_SIZE, remaining);
1173
1174 read = fread (data, 1, readsize, mdatrf->file);
1175 write = fwrite (data, 1, read, outf);
1176 remaining -= read;
1177
1178 if (write != read) {
1179 g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
1180 "Failed to copy data to output file: %s", g_strerror (errno));
1181 goto fail;
1182 }
1183 }
1184 g_free (data);
1185
1186 if (remaining) {
1187 g_set_error (warn, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
1188 "Samples in recovery file were not present on headers."
1189 " Bytes lost: %u", remaining);
1190 } else if (!feof (mdatrf->file)) {
1191 g_set_error (warn, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
1192 "Samples in headers were not found in data file.");
1193 GST_FIXME ("Rewrite mdat size if we reach this to make the file"
1194 " fully correct");
1195 }
1196
1197 return TRUE;
1198
1199 fail:
1200 g_free (stbl_children);
1201 g_free (mvhd_data);
1202 g_free (prefix_data);
1203 g_free (trak_data);
1204 g_free (data);
1205 return FALSE;
1206 }
1207