upload tizen1.0 source
[framework/multimedia/gst-plugins-good0.10.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., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, 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 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 ATOMS_RECOV_OUTPUT_WRITE_ERROR(err) \
92     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE, \
93         "Failed to write to output file: %s", g_strerror (errno))
94
95 static gboolean
96 atoms_recov_write_version (FILE * f)
97 {
98   guint8 data[2];
99   GST_WRITE_UINT16_BE (data, ATOMS_RECOV_FILE_VERSION);
100   return fwrite (data, 2, 1, f) == 1;
101 }
102
103 static gboolean
104 atoms_recov_write_ftyp_info (FILE * f, AtomFTYP * ftyp, GstBuffer * prefix)
105 {
106   guint8 *data = NULL;
107   guint64 offset = 0;
108   guint64 size = 0;
109
110   if (prefix) {
111     if (fwrite (GST_BUFFER_DATA (prefix), 1, GST_BUFFER_SIZE (prefix), f) !=
112         GST_BUFFER_SIZE (prefix)) {
113       return FALSE;
114     }
115   }
116   if (!atom_ftyp_copy_data (ftyp, &data, &size, &offset)) {
117     return FALSE;
118   }
119   if (fwrite (data, 1, offset, f) != offset) {
120     return FALSE;
121   }
122   return TRUE;
123 }
124
125 /**
126  * Writes important info on the 'moov' atom (non-trak related)
127  * to be able to recover the moov structure after a crash.
128  *
129  * Currently, it writes the MVHD atom.
130  */
131 static gboolean
132 atoms_recov_write_moov_info (FILE * f, AtomMOOV * moov)
133 {
134   guint8 *data;
135   guint64 size;
136   guint64 offset = 0;
137   guint64 atom_size = 0;
138   gint writen = 0;
139
140   /* likely enough */
141   size = 256;
142   data = g_malloc (size);
143   atom_size = atom_mvhd_copy_data (&moov->mvhd, &data, &size, &offset);
144   if (atom_size > 0)
145     writen = fwrite (data, 1, atom_size, f);
146   g_free (data);
147   return atom_size > 0 && writen == atom_size;
148 }
149
150 /**
151  * Writes the number of traks to the file.
152  * This simply writes a guint32 in BE.
153  */
154 static gboolean
155 atoms_recov_write_traks_number (FILE * f, guint32 traks)
156 {
157   guint8 data[4];
158   GST_WRITE_UINT32_BE (data, traks);
159   return fwrite (data, 4, 1, f) == 1;
160 }
161
162 /**
163  * Writes the moov's timescale to the file
164  * This simply writes a guint32 in BE.
165  */
166 static gboolean
167 atoms_recov_write_moov_timescale (FILE * f, guint32 timescale)
168 {
169   guint8 data[4];
170   GST_WRITE_UINT32_BE (data, timescale);
171   return fwrite (data, 4, 1, f) == 1;
172 }
173
174 /**
175  * Writes the trak atom to the file.
176  */
177 gboolean
178 atoms_recov_write_trak_info (FILE * f, AtomTRAK * trak)
179 {
180   guint8 *data;
181   guint64 size;
182   guint64 offset = 0;
183   guint64 atom_size = 0;
184   gint writen = 0;
185
186   /* buffer is realloced to a larger size if needed */
187   size = 4 * 1024;
188   data = g_malloc (size);
189   atom_size = atom_trak_copy_data (trak, &data, &size, &offset);
190   if (atom_size > 0)
191     writen = fwrite (data, atom_size, 1, f);
192   g_free (data);
193   return atom_size > 0 && writen == atom_size;
194 }
195
196 gboolean
197 atoms_recov_write_trak_samples (FILE * f, AtomTRAK * trak, guint32 nsamples,
198     guint32 delta, guint32 size, guint64 chunk_offset, gboolean sync,
199     gboolean do_pts, gint64 pts_offset)
200 {
201   guint8 data[TRAK_BUFFER_ENTRY_INFO_SIZE];
202   /*
203    * We have to write a TrakBufferEntryInfo
204    */
205   GST_WRITE_UINT32_BE (data + 0, trak->tkhd.track_ID);
206   GST_WRITE_UINT32_BE (data + 4, nsamples);
207   GST_WRITE_UINT32_BE (data + 8, delta);
208   GST_WRITE_UINT32_BE (data + 12, size);
209   GST_WRITE_UINT64_BE (data + 16, chunk_offset);
210   if (sync)
211     GST_WRITE_UINT8 (data + 24, 1);
212   else
213     GST_WRITE_UINT8 (data + 24, 0);
214   if (do_pts) {
215     GST_WRITE_UINT8 (data + 25, 1);
216     GST_WRITE_UINT64_BE (data + 26, pts_offset);
217   } else {
218     GST_WRITE_UINT8 (data + 25, 0);
219     GST_WRITE_UINT64_BE (data + 26, 0);
220   }
221
222   return fwrite (data, 1, TRAK_BUFFER_ENTRY_INFO_SIZE, f) ==
223       TRAK_BUFFER_ENTRY_INFO_SIZE;
224 }
225
226 gboolean
227 atoms_recov_write_headers (FILE * f, AtomFTYP * ftyp, GstBuffer * prefix,
228     AtomMOOV * moov, guint32 timescale, guint32 traks_number)
229 {
230   if (!atoms_recov_write_version (f)) {
231     return FALSE;
232   }
233
234   if (!atoms_recov_write_ftyp_info (f, ftyp, prefix)) {
235     return FALSE;
236   }
237
238   if (!atoms_recov_write_moov_info (f, moov)) {
239     return FALSE;
240   }
241
242   if (!atoms_recov_write_moov_timescale (f, timescale)) {
243     return FALSE;
244   }
245
246   if (!atoms_recov_write_traks_number (f, traks_number)) {
247     return FALSE;
248   }
249
250   return TRUE;
251 }
252
253 static gboolean
254 read_atom_header (FILE * f, guint32 * fourcc, guint32 * size)
255 {
256   guint8 aux[8];
257
258   if (fread (aux, 1, 8, f) != 8)
259     return FALSE;
260   *size = GST_READ_UINT32_BE (aux);
261   *fourcc = GST_READ_UINT32_LE (aux + 4);
262   return TRUE;
263 }
264
265 static gboolean
266 moov_recov_file_parse_prefix (MoovRecovFile * moovrf)
267 {
268   guint32 fourcc;
269   guint32 size;
270   guint32 total_size = 0;
271   if (fseek (moovrf->file, 2, SEEK_SET) != 0)
272     return FALSE;
273   if (!read_atom_header (moovrf->file, &fourcc, &size)) {
274     return FALSE;
275   }
276
277   if (fourcc != FOURCC_ftyp) {
278     /* we might have a prefix here */
279     if (fseek (moovrf->file, size - 8, SEEK_CUR) != 0)
280       return FALSE;
281
282     total_size += size;
283
284     /* now read the ftyp */
285     if (!read_atom_header (moovrf->file, &fourcc, &size))
286       return FALSE;
287   }
288
289   /* this has to be the ftyp */
290   if (fourcc != FOURCC_ftyp)
291     return FALSE;
292   total_size += size;
293   moovrf->prefix_size = total_size;
294   return fseek (moovrf->file, size - 8, SEEK_CUR) == 0;
295 }
296
297 static gboolean
298 moov_recov_file_parse_mvhd (MoovRecovFile * moovrf)
299 {
300   guint32 fourcc;
301   guint32 size;
302   if (!read_atom_header (moovrf->file, &fourcc, &size)) {
303     return FALSE;
304   }
305   /* check for sanity */
306   if (fourcc != FOURCC_mvhd)
307     return FALSE;
308
309   moovrf->mvhd_size = size;
310   moovrf->mvhd_pos = ftell (moovrf->file) - 8;
311
312   /* skip the remaining of the mvhd in the file */
313   return fseek (moovrf->file, size - 8, SEEK_CUR) == 0;
314 }
315
316 static gboolean
317 mdat_recov_file_parse_mdat_start (MdatRecovFile * mdatrf)
318 {
319   guint32 fourcc, size;
320
321   if (!read_atom_header (mdatrf->file, &fourcc, &size)) {
322     return FALSE;
323   }
324   if (size == 1) {
325     mdatrf->mdat_header_size = 16;
326     mdatrf->mdat_size = 16;
327   } else {
328     mdatrf->mdat_header_size = 8;
329     mdatrf->mdat_size = 8;
330   }
331   mdatrf->mdat_start = ftell (mdatrf->file) - 8;
332
333   return fourcc == FOURCC_mdat;
334 }
335
336 MdatRecovFile *
337 mdat_recov_file_create (FILE * file, gboolean datafile, GError ** err)
338 {
339   MdatRecovFile *mrf = g_new0 (MdatRecovFile, 1);
340   guint32 fourcc, size;
341
342   g_return_val_if_fail (file != NULL, NULL);
343
344   mrf->file = file;
345   mrf->rawfile = datafile;
346
347   /* get the file/data length */
348   if (fseek (file, 0, SEEK_END) != 0)
349     goto file_length_error;
350   /* still needs to deduce the mdat header and ftyp size */
351   mrf->data_size = ftell (file);
352   if (mrf->data_size == -1L)
353     goto file_length_error;
354
355   if (fseek (file, 0, SEEK_SET) != 0)
356     goto file_seek_error;
357
358   if (datafile) {
359     /* this file contains no atoms, only raw data to be placed on the mdat
360      * this happens when faststart mode is used */
361     mrf->mdat_start = 0;
362     mrf->mdat_header_size = 16;
363     mrf->mdat_size = 16;
364     return mrf;
365   }
366
367   if (!read_atom_header (file, &fourcc, &size)) {
368     goto parse_error;
369   }
370   if (fourcc != FOURCC_ftyp) {
371     /* this could be a prefix atom, let's skip it and try again */
372     if (fseek (file, size - 8, SEEK_CUR) != 0) {
373       goto file_seek_error;
374     }
375     if (!read_atom_header (file, &fourcc, &size)) {
376       goto parse_error;
377     }
378   }
379
380   if (fourcc != FOURCC_ftyp) {
381     goto parse_error;
382   }
383   if (fseek (file, size - 8, SEEK_CUR) != 0)
384     goto file_seek_error;
385
386   /* we don't parse this if we have a tmpdatafile */
387   if (!mdat_recov_file_parse_mdat_start (mrf)) {
388     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
389         "Error while parsing mdat atom");
390     goto fail;
391   }
392
393   return mrf;
394
395 parse_error:
396   g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
397       "Failed to parse atom");
398   goto fail;
399
400 file_seek_error:
401   g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
402       "Failed to seek to start of the file");
403   goto fail;
404
405 file_length_error:
406   g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
407       "Failed to determine file size");
408   goto fail;
409
410 fail:
411   mdat_recov_file_free (mrf);
412   return NULL;
413 }
414
415 void
416 mdat_recov_file_free (MdatRecovFile * mrf)
417 {
418   fclose (mrf->file);
419   g_free (mrf);
420 }
421
422 static gboolean
423 moov_recov_parse_num_traks (MoovRecovFile * moovrf)
424 {
425   guint8 traks[4];
426   if (fread (traks, 1, 4, moovrf->file) != 4)
427     return FALSE;
428   moovrf->num_traks = GST_READ_UINT32_BE (traks);
429   return TRUE;
430 }
431
432 static gboolean
433 moov_recov_parse_moov_timescale (MoovRecovFile * moovrf)
434 {
435   guint8 ts[4];
436   if (fread (ts, 1, 4, moovrf->file) != 4)
437     return FALSE;
438   moovrf->timescale = GST_READ_UINT32_BE (ts);
439   return TRUE;
440 }
441
442 static gboolean
443 skip_atom (MoovRecovFile * moovrf, guint32 expected_fourcc)
444 {
445   guint32 size;
446   guint32 fourcc;
447
448   if (!read_atom_header (moovrf->file, &fourcc, &size))
449     return FALSE;
450   if (fourcc != expected_fourcc)
451     return FALSE;
452
453   return (fseek (moovrf->file, size - 8, SEEK_CUR) == 0);
454 }
455
456 static gboolean
457 moov_recov_parse_tkhd (MoovRecovFile * moovrf, TrakRecovData * trakrd)
458 {
459   guint32 size;
460   guint32 fourcc;
461   guint8 data[4];
462
463   /* make sure we are on a tkhd atom */
464   if (!read_atom_header (moovrf->file, &fourcc, &size))
465     return FALSE;
466   if (fourcc != FOURCC_tkhd)
467     return FALSE;
468
469   trakrd->tkhd_file_offset = ftell (moovrf->file) - 8;
470
471   /* move 8 bytes forward to the trak_id pos */
472   if (fseek (moovrf->file, 12, SEEK_CUR) != 0)
473     return FALSE;
474   if (fread (data, 1, 4, moovrf->file) != 4)
475     return FALSE;
476
477   /* advance the rest of tkhd */
478   fseek (moovrf->file, 68, SEEK_CUR);
479
480   trakrd->trak_id = GST_READ_UINT32_BE (data);
481   return TRUE;
482 }
483
484 static gboolean
485 moov_recov_parse_stbl (MoovRecovFile * moovrf, TrakRecovData * trakrd)
486 {
487   guint32 size;
488   guint32 fourcc;
489   guint32 auxsize;
490
491   if (!read_atom_header (moovrf->file, &fourcc, &size))
492     return FALSE;
493   if (fourcc != FOURCC_stbl)
494     return FALSE;
495
496   trakrd->stbl_file_offset = ftell (moovrf->file) - 8;
497   trakrd->stbl_size = size;
498
499   /* skip the stsd */
500   if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
501     return FALSE;
502   if (fourcc != FOURCC_stsd)
503     return FALSE;
504   if (fseek (moovrf->file, auxsize - 8, SEEK_CUR) != 0)
505     return FALSE;
506
507   trakrd->stsd_size = auxsize;
508   trakrd->post_stsd_offset = ftell (moovrf->file);
509
510   /* as this is the last atom we parse, we don't skip forward */
511
512   return TRUE;
513 }
514
515 static gboolean
516 moov_recov_parse_minf (MoovRecovFile * moovrf, TrakRecovData * trakrd)
517 {
518   guint32 size;
519   guint32 fourcc;
520   guint32 auxsize;
521
522   if (!read_atom_header (moovrf->file, &fourcc, &size))
523     return FALSE;
524   if (fourcc != FOURCC_minf)
525     return FALSE;
526
527   trakrd->minf_file_offset = ftell (moovrf->file) - 8;
528   trakrd->minf_size = size;
529
530   /* skip either of vmhd, smhd, hmhd that might follow */
531   if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
532     return FALSE;
533   if (fourcc != FOURCC_vmhd && fourcc != FOURCC_smhd && fourcc != FOURCC_hmhd &&
534       fourcc != FOURCC_gmhd)
535     return FALSE;
536   if (fseek (moovrf->file, auxsize - 8, SEEK_CUR))
537     return FALSE;
538
539   /* skip a possible hdlr and the following dinf */
540   if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
541     return FALSE;
542   if (fourcc == FOURCC_hdlr) {
543     if (fseek (moovrf->file, auxsize - 8, SEEK_CUR))
544       return FALSE;
545     if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
546       return FALSE;
547   }
548   if (fourcc != FOURCC_dinf)
549     return FALSE;
550   if (fseek (moovrf->file, auxsize - 8, SEEK_CUR))
551     return FALSE;
552
553   /* now we are ready to read the stbl */
554   if (!moov_recov_parse_stbl (moovrf, trakrd))
555     return FALSE;
556
557   return TRUE;
558 }
559
560 static gboolean
561 moov_recov_parse_mdhd (MoovRecovFile * moovrf, TrakRecovData * trakrd)
562 {
563   guint32 size;
564   guint32 fourcc;
565   guint8 data[4];
566
567   /* make sure we are on a tkhd atom */
568   if (!read_atom_header (moovrf->file, &fourcc, &size))
569     return FALSE;
570   if (fourcc != FOURCC_mdhd)
571     return FALSE;
572
573   trakrd->mdhd_file_offset = ftell (moovrf->file) - 8;
574
575   /* get the timescale */
576   if (fseek (moovrf->file, 12, SEEK_CUR) != 0)
577     return FALSE;
578   if (fread (data, 1, 4, moovrf->file) != 4)
579     return FALSE;
580   trakrd->timescale = GST_READ_UINT32_BE (data);
581   if (fseek (moovrf->file, 8, SEEK_CUR) != 0)
582     return FALSE;
583   return TRUE;
584 }
585
586 static gboolean
587 moov_recov_parse_mdia (MoovRecovFile * moovrf, TrakRecovData * trakrd)
588 {
589   guint32 size;
590   guint32 fourcc;
591
592   /* make sure we are on a tkhd atom */
593   if (!read_atom_header (moovrf->file, &fourcc, &size))
594     return FALSE;
595   if (fourcc != FOURCC_mdia)
596     return FALSE;
597
598   trakrd->mdia_file_offset = ftell (moovrf->file) - 8;
599   trakrd->mdia_size = size;
600
601   if (!moov_recov_parse_mdhd (moovrf, trakrd))
602     return FALSE;
603
604   if (!skip_atom (moovrf, FOURCC_hdlr))
605     return FALSE;
606   if (!moov_recov_parse_minf (moovrf, trakrd))
607     return FALSE;
608   return TRUE;
609 }
610
611 static gboolean
612 moov_recov_parse_trak (MoovRecovFile * moovrf, TrakRecovData * trakrd)
613 {
614   guint64 offset;
615   guint32 size;
616   guint32 fourcc;
617
618   offset = ftell (moovrf->file);
619   if (offset == -1) {
620     return FALSE;
621   }
622
623   /* make sure we are on a trak atom */
624   if (!read_atom_header (moovrf->file, &fourcc, &size)) {
625     return FALSE;
626   }
627   if (fourcc != FOURCC_trak) {
628     return FALSE;
629   }
630   trakrd->trak_size = size;
631
632   /* now we should have a trak header 'tkhd' */
633   if (!moov_recov_parse_tkhd (moovrf, trakrd))
634     return FALSE;
635
636   /* FIXME add edts handling here and in qtmux, as this is only detected
637    * after buffers start flowing */
638
639   if (!moov_recov_parse_mdia (moovrf, trakrd))
640     return FALSE;
641
642   trakrd->file_offset = offset;
643   /* position after the trak */
644   return fseek (moovrf->file, (long int) offset + size, SEEK_SET) == 0;
645 }
646
647 MoovRecovFile *
648 moov_recov_file_create (FILE * file, GError ** err)
649 {
650   gint i;
651   MoovRecovFile *moovrf = g_new0 (MoovRecovFile, 1);
652
653   g_return_val_if_fail (file != NULL, NULL);
654
655   moovrf->file = file;
656
657   /* look for ftyp and prefix at the start */
658   if (!moov_recov_file_parse_prefix (moovrf)) {
659     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
660         "Error while parsing prefix atoms");
661     goto fail;
662   }
663
664   /* parse the mvhd */
665   if (!moov_recov_file_parse_mvhd (moovrf)) {
666     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
667         "Error while parsing mvhd atom");
668     goto fail;
669   }
670
671   if (!moov_recov_parse_moov_timescale (moovrf)) {
672     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
673         "Error while parsing timescale");
674     goto fail;
675   }
676   if (!moov_recov_parse_num_traks (moovrf)) {
677     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
678         "Error while parsing parsing number of traks");
679     goto fail;
680   }
681
682   /* init the traks */
683   moovrf->traks_rd = g_new0 (TrakRecovData, moovrf->num_traks);
684   for (i = 0; i < moovrf->num_traks; i++) {
685     atom_stbl_init (&(moovrf->traks_rd[i].stbl));
686   }
687   for (i = 0; i < moovrf->num_traks; i++) {
688     if (!moov_recov_parse_trak (moovrf, &(moovrf->traks_rd[i]))) {
689       g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
690           "Error while parsing trak atom");
691       goto fail;
692     }
693   }
694
695   return moovrf;
696
697 fail:
698   moov_recov_file_free (moovrf);
699   return NULL;
700 }
701
702 void
703 moov_recov_file_free (MoovRecovFile * moovrf)
704 {
705   gint i;
706   fclose (moovrf->file);
707   if (moovrf->traks_rd) {
708     for (i = 0; i < moovrf->num_traks; i++) {
709       atom_stbl_clear (&(moovrf->traks_rd[i].stbl));
710     }
711     g_free (moovrf->traks_rd);
712   }
713   g_free (moovrf);
714 }
715
716 static gboolean
717 moov_recov_parse_buffer_entry (MoovRecovFile * moovrf, TrakBufferEntryInfo * b)
718 {
719   guint8 data[TRAK_BUFFER_ENTRY_INFO_SIZE];
720   gint read;
721
722   read = fread (data, 1, TRAK_BUFFER_ENTRY_INFO_SIZE, moovrf->file);
723   if (read != TRAK_BUFFER_ENTRY_INFO_SIZE)
724     return FALSE;
725
726   b->track_id = GST_READ_UINT32_BE (data);
727   b->nsamples = GST_READ_UINT32_BE (data + 4);
728   b->delta = GST_READ_UINT32_BE (data + 8);
729   b->size = GST_READ_UINT32_BE (data + 12);
730   b->chunk_offset = GST_READ_UINT64_BE (data + 16);
731   b->sync = data[24] != 0;
732   b->do_pts = data[25] != 0;
733   b->pts_offset = GST_READ_UINT64_BE (data + 26);
734   return TRUE;
735 }
736
737 static gboolean
738 mdat_recov_add_sample (MdatRecovFile * mdatrf, guint32 size)
739 {
740   /* test if this data exists */
741   if (mdatrf->mdat_size - mdatrf->mdat_header_size + size > mdatrf->data_size)
742     return FALSE;
743
744   mdatrf->mdat_size += size;
745   return TRUE;
746 }
747
748 static TrakRecovData *
749 moov_recov_get_trak (MoovRecovFile * moovrf, guint32 id)
750 {
751   gint i;
752   for (i = 0; i < moovrf->num_traks; i++) {
753     if (moovrf->traks_rd[i].trak_id == id)
754       return &(moovrf->traks_rd[i]);
755   }
756   return NULL;
757 }
758
759 static void
760 trak_recov_data_add_sample (TrakRecovData * trak, TrakBufferEntryInfo * b)
761 {
762   trak->duration += b->nsamples * b->delta;
763   atom_stbl_add_samples (&trak->stbl, b->nsamples, b->delta, b->size,
764       b->chunk_offset, b->sync, b->pts_offset);
765 }
766
767 /**
768  * Parses the buffer entries in the MoovRecovFile and matches the inputs
769  * with the data in the MdatRecovFile. Whenever a buffer entry of that
770  * represents 'x' bytes of data, the same amount of data is 'validated' in
771  * the MdatRecovFile and will be inluded in the generated moovie file.
772  */
773 gboolean
774 moov_recov_parse_buffers (MoovRecovFile * moovrf, MdatRecovFile * mdatrf,
775     GError ** err)
776 {
777   TrakBufferEntryInfo entry;
778   TrakRecovData *trak;
779
780   /* we assume both moovrf and mdatrf are at the starting points of their
781    * data reading */
782   while (moov_recov_parse_buffer_entry (moovrf, &entry)) {
783     /* be sure we still have this data in mdat */
784     trak = moov_recov_get_trak (moovrf, entry.track_id);
785     if (trak == NULL) {
786       g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
787           "Invalid trak id found in buffer entry");
788       return FALSE;
789     }
790     if (!mdat_recov_add_sample (mdatrf, entry.size))
791       break;
792     trak_recov_data_add_sample (trak, &entry);
793   }
794   return TRUE;
795 }
796
797 static guint32
798 trak_recov_data_get_trak_atom_size (TrakRecovData * trak)
799 {
800   AtomSTBL *stbl = &trak->stbl;
801   guint64 offset;
802
803   /* write out our stbl child atoms */
804   offset = 0;
805
806   if (!atom_stts_copy_data (&stbl->stts, NULL, NULL, &offset)) {
807     goto fail;
808   }
809   if (atom_array_get_len (&stbl->stss.entries) > 0) {
810     if (!atom_stss_copy_data (&stbl->stss, NULL, NULL, &offset)) {
811       goto fail;
812     }
813   }
814   if (!atom_stsc_copy_data (&stbl->stsc, NULL, NULL, &offset)) {
815     goto fail;
816   }
817   if (!atom_stsz_copy_data (&stbl->stsz, NULL, NULL, &offset)) {
818     goto fail;
819   }
820   if (stbl->ctts) {
821     if (!atom_ctts_copy_data (stbl->ctts, NULL, NULL, &offset)) {
822       goto fail;
823     }
824   }
825   if (!atom_stco64_copy_data (&stbl->stco64, NULL, NULL, &offset)) {
826     goto fail;
827   }
828
829   return trak->trak_size + ((trak->stsd_size + offset + 8) - trak->stbl_size);
830
831 fail:
832   return 0;
833 }
834
835 static guint8 *
836 moov_recov_get_stbl_children_data (MoovRecovFile * moovrf, TrakRecovData * trak,
837     guint64 * p_size)
838 {
839   AtomSTBL *stbl = &trak->stbl;
840   guint8 *buffer;
841   guint64 size;
842   guint64 offset;
843
844   /* write out our stbl child atoms
845    *
846    * Use 1MB as a starting size, *_copy_data functions
847    * will grow the buffer if needed.
848    */
849   size = 1024 * 1024;
850   buffer = g_malloc0 (size);
851   offset = 0;
852
853   if (!atom_stts_copy_data (&stbl->stts, &buffer, &size, &offset)) {
854     goto fail;
855   }
856   if (atom_array_get_len (&stbl->stss.entries) > 0) {
857     if (!atom_stss_copy_data (&stbl->stss, &buffer, &size, &offset)) {
858       goto fail;
859     }
860   }
861   if (!atom_stsc_copy_data (&stbl->stsc, &buffer, &size, &offset)) {
862     goto fail;
863   }
864   if (!atom_stsz_copy_data (&stbl->stsz, &buffer, &size, &offset)) {
865     goto fail;
866   }
867   if (stbl->ctts) {
868     if (!atom_ctts_copy_data (stbl->ctts, &buffer, &size, &offset)) {
869       goto fail;
870     }
871   }
872   if (!atom_stco64_copy_data (&stbl->stco64, &buffer, &size, &offset)) {
873     goto fail;
874   }
875   *p_size = offset;
876   return buffer;
877
878 fail:
879   g_free (buffer);
880   return NULL;
881 }
882
883 gboolean
884 moov_recov_write_file (MoovRecovFile * moovrf, MdatRecovFile * mdatrf,
885     FILE * outf, GError ** err)
886 {
887   guint8 auxdata[16];
888   guint8 *data = NULL;
889   guint8 *prefix_data = NULL;
890   guint8 *mvhd_data = NULL;
891   guint8 *trak_data = NULL;
892   guint32 moov_size = 0;
893   gint i;
894   guint64 stbl_children_size = 0;
895   guint8 *stbl_children = NULL;
896   guint32 longest_duration = 0;
897   guint16 version;
898
899   /* check the version */
900   if (fseek (moovrf->file, 0, SEEK_SET) != 0) {
901     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
902         "Failed to seek to the start of the moov recovery file");
903     goto fail;
904   }
905   if (fread (auxdata, 1, 2, moovrf->file) != 2) {
906     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
907         "Failed to read version from file");
908   }
909
910   version = GST_READ_UINT16_BE (auxdata);
911   if (version != ATOMS_RECOV_FILE_VERSION) {
912     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_VERSION,
913         "Input file version (%u) is not supported in this version (%u)",
914         version, ATOMS_RECOV_FILE_VERSION);
915     return FALSE;
916   }
917
918   /* write the ftyp */
919   prefix_data = g_malloc (moovrf->prefix_size);
920   if (fread (prefix_data, 1, moovrf->prefix_size,
921           moovrf->file) != moovrf->prefix_size) {
922     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
923         "Failed to read the ftyp atom from file");
924     goto fail;
925   }
926   if (fwrite (prefix_data, 1, moovrf->prefix_size, outf) != moovrf->prefix_size) {
927     ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
928     goto fail;
929   }
930   g_free (prefix_data);
931   prefix_data = NULL;
932
933   /* need to calculate the moov size beforehand to add the offset to
934    * chunk offset entries */
935   moov_size += moovrf->mvhd_size + 8;   /* mvhd + moov size + fourcc */
936   for (i = 0; i < moovrf->num_traks; i++) {
937     TrakRecovData *trak = &(moovrf->traks_rd[i]);
938     guint32 duration;           /* in moov's timescale */
939     guint32 trak_size;
940
941     /* convert trak duration to moov's duration */
942     duration = gst_util_uint64_scale_round (trak->duration, moovrf->timescale,
943         trak->timescale);
944
945     if (duration > longest_duration)
946       longest_duration = duration;
947     trak_size = trak_recov_data_get_trak_atom_size (trak);
948     if (trak_size == 0) {
949       g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_GENERIC,
950           "Failed to estimate trak atom size");
951       goto fail;
952     }
953     moov_size += trak_size;
954   }
955
956   /* add chunks offsets */
957   for (i = 0; i < moovrf->num_traks; i++) {
958     TrakRecovData *trak = &(moovrf->traks_rd[i]);
959     /* 16 for the mdat header */
960     gint64 offset = moov_size + ftell (outf) + 16;
961     atom_stco64_chunks_add_offset (&trak->stbl.stco64, offset);
962   }
963
964   /* write the moov */
965   GST_WRITE_UINT32_BE (auxdata, moov_size);
966   GST_WRITE_UINT32_LE (auxdata + 4, FOURCC_moov);
967   if (fwrite (auxdata, 1, 8, outf) != 8) {
968     ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
969     goto fail;
970   }
971
972   /* write the mvhd */
973   mvhd_data = g_malloc (moovrf->mvhd_size);
974   if (fseek (moovrf->file, moovrf->mvhd_pos, SEEK_SET) != 0)
975     goto fail;
976   if (fread (mvhd_data, 1, moovrf->mvhd_size,
977           moovrf->file) != moovrf->mvhd_size)
978     goto fail;
979   GST_WRITE_UINT32_BE (mvhd_data + 20, moovrf->timescale);
980   GST_WRITE_UINT32_BE (mvhd_data + 24, longest_duration);
981   if (fwrite (mvhd_data, 1, moovrf->mvhd_size, outf) != moovrf->mvhd_size) {
982     ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
983     goto fail;
984   }
985   g_free (mvhd_data);
986   mvhd_data = NULL;
987
988   /* write the traks, this is the tough part because we need to update:
989    * - stbl atom
990    * - sizes of atoms from stbl to trak
991    * - trak duration
992    */
993   for (i = 0; i < moovrf->num_traks; i++) {
994     TrakRecovData *trak = &(moovrf->traks_rd[i]);
995     guint trak_data_size;
996     guint32 stbl_new_size;
997     guint32 minf_new_size;
998     guint32 mdia_new_size;
999     guint32 trak_new_size;
1000     guint32 size_diff;
1001     guint32 duration;           /* in moov's timescale */
1002
1003     /* convert trak duration to moov's duration */
1004     duration = gst_util_uint64_scale_round (trak->duration, moovrf->timescale,
1005         trak->timescale);
1006
1007     stbl_children = moov_recov_get_stbl_children_data (moovrf, trak,
1008         &stbl_children_size);
1009     if (stbl_children == NULL)
1010       goto fail;
1011
1012     /* calc the new size of the atoms from stbl to trak in the atoms tree */
1013     stbl_new_size = trak->stsd_size + stbl_children_size + 8;
1014     size_diff = stbl_new_size - trak->stbl_size;
1015     minf_new_size = trak->minf_size + size_diff;
1016     mdia_new_size = trak->mdia_size + size_diff;
1017     trak_new_size = trak->trak_size + size_diff;
1018
1019     if (fseek (moovrf->file, trak->file_offset, SEEK_SET) != 0)
1020       goto fail;
1021     trak_data_size = trak->post_stsd_offset - trak->file_offset;
1022     trak_data = g_malloc (trak_data_size);
1023     if (fread (trak_data, 1, trak_data_size, moovrf->file) != trak_data_size) {
1024       goto fail;
1025     }
1026     /* update the size values in those read atoms before writing */
1027     GST_WRITE_UINT32_BE (trak_data, trak_new_size);
1028     GST_WRITE_UINT32_BE (trak_data + (trak->mdia_file_offset -
1029             trak->file_offset), mdia_new_size);
1030     GST_WRITE_UINT32_BE (trak_data + (trak->minf_file_offset -
1031             trak->file_offset), minf_new_size);
1032     GST_WRITE_UINT32_BE (trak_data + (trak->stbl_file_offset -
1033             trak->file_offset), stbl_new_size);
1034
1035     /* update duration values in tkhd and mdhd */
1036     GST_WRITE_UINT32_BE (trak_data + (trak->tkhd_file_offset -
1037             trak->file_offset) + 28, duration);
1038     GST_WRITE_UINT32_BE (trak_data + (trak->mdhd_file_offset -
1039             trak->file_offset) + 24, trak->duration);
1040
1041     if (fwrite (trak_data, 1, trak_data_size, outf) != trak_data_size) {
1042       ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1043       goto fail;
1044     }
1045     if (fwrite (stbl_children, 1, stbl_children_size, outf) !=
1046         stbl_children_size) {
1047       ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1048       goto fail;
1049     }
1050     g_free (trak_data);
1051     trak_data = NULL;
1052     g_free (stbl_children);
1053     stbl_children = NULL;
1054   }
1055
1056   /* write the mdat */
1057   /* write the header first */
1058   GST_WRITE_UINT32_BE (auxdata, 1);
1059   GST_WRITE_UINT32_LE (auxdata + 4, FOURCC_mdat);
1060   GST_WRITE_UINT64_BE (auxdata + 8, mdatrf->mdat_size);
1061   if (fwrite (auxdata, 1, 16, outf) != 16) {
1062     ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1063     goto fail;
1064   }
1065
1066   /* now read the mdat data and output to the file */
1067   if (fseek (mdatrf->file, mdatrf->mdat_start +
1068           (mdatrf->rawfile ? 0 : mdatrf->mdat_header_size), SEEK_SET) != 0)
1069     goto fail;
1070
1071   data = g_malloc (4096);
1072   while (!feof (mdatrf->file)) {
1073     gint read, write;
1074
1075     read = fread (data, 1, 4096, mdatrf->file);
1076     write = fwrite (data, 1, read, outf);
1077
1078     if (write != read) {
1079       g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
1080           "Failed to copy data to output file: %s", g_strerror (errno));
1081       goto fail;
1082     }
1083   }
1084   g_free (data);
1085
1086   return TRUE;
1087
1088 fail:
1089   g_free (stbl_children);
1090   g_free (mvhd_data);
1091   g_free (prefix_data);
1092   g_free (trak_data);
1093   g_free (data);
1094   return FALSE;
1095 }