qtdemux: Avoid warning when reporting about decryptors
[platform/upstream/gst-plugins-good.git] / gst / isomp4 / atomsrecovery.c
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
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
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
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
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
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
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
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
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
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
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
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
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
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 *
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
453 mdat_recov_file_free (MdatRecovFile * mrf)
454 {
455   fclose (mrf->file);
456   g_free (mrf);
457 }
458
459 static gboolean
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
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
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
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
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
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
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
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
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 *
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
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
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
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 *
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
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
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
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 *
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
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
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 }